Vue3选项式API基础咋理解?
很多刚接触Vue3的同学,一上来就被选项式API和组合式API搞晕:明明Vue2一直用选项式,Vue3里这玩意儿还能用不?和组合式比到底咋选?开发时要注意啥?今天就唠明白Vue3选项式API的那些事儿,从基础到实战,帮你理清思路~
选项式API是Vue3保留的经典写法,和Vue2一脉相承,在Vue3里,你依然能像Vue2那样,把组件逻辑拆成data
、methods
、computed
、watch
这些“选项”来组织,比如写个简单的计数器组件:
<template> <button @click="increment">{{ count }}</button> </template> <script> export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } } } </script>
这里data
返回响应式数据,methods
放方法,this
指向组件实例——和Vue2的逻辑完全打通,Vue3保留选项式API,一是照顾老项目迁移(直接升级Vue3时,代码改动少);二是给习惯“按功能分块写代码”的开发者安全感——不用重构整个项目,也能享受Vue3的性能优化(比如响应式系统重构、Tree-shaking支持)。
不过Vue3选项式里的API有小变化:比如生命周期钩子,Vue2的beforeDestroy
改成beforeUnmount
,destroyed
改成unmounted
;自定义指令的钩子也有调整(bind
→beforeMount
,inserted
→mounted
等),这些细节看官方文档就能快速摸清~
选项式和组合式API核心区别是啥?
这两种API的设计思路天差地别,从代码组织、逻辑复用,到响应式原理、TypeScript支持,处处都有差异,咱逐个掰扯:
代码组织逻辑
选项式是“按功能分类”:把数据(data
)、方法(methods
)、计算属性(computed
)等,分别丢进不同选项里,好处是结构清晰,新手看一眼就知道“数据在data
,方法在methods
”;但组件逻辑复杂时,同一个功能的代码可能分散在多个选项里(比如一个表单验证,数据在data
、方法在methods
、侦听器在watch
),来回跳着看代码特累。
组合式API是“按逻辑聚合”:用setup
函数把相关逻辑打包,比如一个用户信息模块,把数据、修改数据的方法、侦听器全塞一块:
<script setup> import { ref, watch } from 'vue' const user = ref({ name: '张三' }) const updateName = () => { user.value.name = '李四' } watch(user, (newVal) => { console.log('用户变化:', newVal) }) </script>
这样“用户模块”的逻辑全在setup
里扎堆,维护时不用满文件找代码,适合复杂组件。
逻辑复用方式
选项式靠mixins
复用逻辑:比如把表单验证逻辑写在mixin
里,多个组件引入,但问题一堆:不同mixin
里的data
、methods
重名会冲突;组件里分不清哪个逻辑来自哪个mixin
,调试时像“黑盒”。
组合式用Composables
(组合函数):把复用逻辑封装成函数,比如useFormValidation
,组件里按需引入,逻辑来源清晰,也没命名冲突风险,举个例子:
// useFormValidation.js import { ref, computed } from 'vue' export function useFormValidation() { const formData = ref({ username: '', password: '' }) const isFormValid = computed(() => { return formData.value.username && formData.value.password }) return { formData, isFormValid } } // 组件里使用 <script setup> import { useFormValidation } from './useFormValidation.js' const { formData, isFormValid } = useFormValidation() </script>
每个组合函数的逻辑独立,复用起来丝滑多了~
响应式原理差异
选项式里,data
返回的对象会被Vue自动加上响应式“魔法”,直接通过this.xxx
就能访问/修改,Vue能自动追踪依赖,但要注意:直接改数组索引(比如this.list[0] = 1
)、给对象新增属性(this.obj.newKey = 'val'
),Vue检测不到,得用this.$set
(Vue2写法,Vue3选项式里也支持)。
组合式API里,得手动用ref
(基本类型)、reactive
(对象/数组)来声明响应式数据,修改时要通过.value
(ref
)或者直接改(reactive
对象,但深层响应式要注意)。
<!-- 组合式 --> <script setup> import { ref } from 'vue' const count = ref(0) const increment = () => { count.value++ } // 必须写 .value </script> <!-- 选项式 --> <script> export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } } // 直接 this.xxx } </script>
TypeScript支持友好度
选项式API在TS里写类型,得给props
、data
做额外声明,
<script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({ props: { { type: String, required: true } }, data() { return { count: 0 as number // 得手动断言类型 } }, methods: { increment() { /* ... */ } } }) </script>
组合式API用defineProps
、defineEmits
能直接推断类型,ref
和reactive
也能通过泛型自动推导,对TS更友好。
<script setup lang="ts"> const props = defineProps<{ title: string }>() const count = ref(0) // 自动推断 number 类型 </script>
所以如果项目重度用TS,组合式写起来更顺手~
选项式API开发常见场景咋处理?
日常开发逃不过组件通信、生命周期、计算属性这些,选项式里咋玩?一个个说:
组件通信:父→子、子→父、跨层级
-
父传子(
props
):和Vue2一样,在props
选项声明接收的参数,类型、默认值都能配:<script> export default { props: { list: { type: Array, default: () => [] }, title: String } } </script>
-
子传父(
emits
):在emits
选项声明事件,然后this.$emit('事件名', 数据)
:<script> export default { emits: ['item-click'], methods: { handleClick(item) { this.$emit('item-click', item) } } } </script>
-
跨层级(
provide/inject
):祖先组件provide
数据,后代组件inject
接收:祖先组件:
<script> export default { provide() { return { theme: 'dark' } } } </script>
后代组件:
<script> export default { inject: ['theme'], mounted() { console.log('当前主题:', this.theme) } } </script>
生命周期钩子咋用?
选项式里的生命周期和Vue2名字大部分一样,只是Vue3把“销毁阶段”改成“卸载阶段”了:
Vue2生命周期 | Vue3选项式生命周期 | 触发时机 |
---|---|---|
beforeCreate |
beforeCreate |
组件创建前 |
created |
created |
组件创建后 |
beforeMount |
beforeMount |
挂载到DOM前 |
mounted |
mounted |
挂载到DOM后 |
beforeUpdate |
beforeUpdate |
数据更新,DOM更新前 |
updated |
updated |
数据更新,DOM更新后 |
beforeDestroy |
beforeUnmount |
组件卸载前 |
destroyed |
unmounted |
组件卸载后 |
用的时候直接在选项里写钩子函数:
<script> export default { mounted() { console.log('组件挂载完成,能操作DOM啦~') }, beforeUnmount() { console.log('组件要卸载了,清理定时器、事件监听之类的~') } } </script>
计算属性(computed
)和侦听器(watch
)
-
computed
:处理依赖数据的“自动计算”,比如购物车总价:<script> export default { data() { return { goods: [{ price: 10, num: 2 }, { price: 20, num: 1 }] } }, computed: { totalPrice() { return this.goods.reduce((sum, item) => sum + item.price * item.num, 0) } } } </script>
依赖的
goods
变化时,totalPrice
会自动更新~ -
watch
:监听数据变化做副作用操作,比如监听搜索关键词发请求:<script> export default { data() { return { searchKey: '' } }, watch: { searchKey(newVal, oldVal) { if (newVal) { // 发请求获取搜索结果 } } } } </script>
还能深度监听对象/数组:
watch: { 'user.info'(newVal) { // 监听 user.info 子属性 console.log('用户信息变化:', newVal) }, goods: { handler(newVal) { /* ... */ }, deep: true // 深度监听数组/对象内部变化 } }
自定义指令(directives
)
选项式里通过directives
选项注册自定义指令,比如做个“自动聚焦”指令:
<template> <input v-focus /> </template> <script> export default { directives: { focus: { mounted(el) { // 指令绑定的元素挂载后触发 el.focus() } } } } </script>
指令的钩子函数和生命周期对应,比如beforeMount
(元素挂载前)、mounted
(元素挂载后)等,和Vue3的指令钩子规则一致~
新手用选项式容易踩哪些坑?
选项式看似和Vue2差不多,但Vue3里有些细节没注意,容易掉坑里,这些“雷区”得避开:
this
指向丢了!
在选项式里,methods
里的函数默认绑定组件实例的this
,但如果用箭头函数,this
就不是组件实例了!
<script> export default { data() { return { count: 0 } }, methods: { increment: () => { this.count++ // 这里this是window(非严格模式)或undefined(严格模式),直接报错! } } } </script>
所以methods
里必须用普通函数,别用箭头函数!
响应式数据更新不生效
选项式里data
的响应式是Vue自动处理的,但直接修改数组索引、给对象加新属性,Vue检测不到变化!
<script> export default { data() { return { list: [1,2,3], user: { name: '张三' } } }, methods: { wrongUpdate() { this.list[0] = 10 // Vue检测不到,页面不更新 this.user.age = 18 // user原本没age属性,新增的属性不是响应式的,页面也不更新 } } } </script>
解决办法:
- 数组更新用
push
、splice
等变异方法,或者替换整个数组(比如this.list = [...this.list.slice(0,0), 10, ...this.list.slice(1)]
); - 对象新增属性用
this.$set(this.user, 'age', 18)
(Vue3选项式里也支持this.$set
,和Vue2一样)。
mixins
带来的“隐形Bug”
用mixins
复用逻辑时,容易出现命名冲突(比如两个mixin
都有handleClick
方法,组件里会覆盖),而且逻辑来源不透明(组件里分不清哪个方法来自哪个mixin
)。
// mixin1.js export default { methods: { handleClick() { console.log('mixin1的点击') } } } // mixin2.js export default { methods: { handleClick() { console.log('mixin2的点击') } } } // 组件里同时引入 <script> import mixin1 from './mixin1.js' import mixin2 from './mixin2.js' export default { mixins: [mixin1, mixin2], methods: { // 这里handleClick会被mixin2覆盖,自己写的也会被覆盖,逻辑乱成粥 } } </script>
所以除非维护老项目,尽量少用mixins
,改用组合式的Composables
,逻辑清晰不冲突~
TypeScript里类型声明麻烦
选项式在TS中给data
、props
写类型,得手动断言或用defineComponent
配合类型接口,比组合式的自动推导麻烦,比如给data
加类型:
<script lang="ts"> import { defineComponent } from 'vue' interface User { name: string; age: number } export default defineComponent({ data() { return { user: { name: '张三', age: 18 } as User // 得手动as断言,否则TS推断成any } } }) </script>
如果是组合式,用ref<User>({ name: '张三', age: 18 })
就能自动推导类型,省心多了~
啥时候选选项式而非组合式?
不是所有场景都适合组合式,这几种情况选选项式更舒服:
维护Vue2迁移过来的老项目
团队要把Vue2项目升级到Vue3,用选项式API能最大限度保留原有代码结构,只需要处理少数 breaking changes(比如生命周期改名、指令钩子变化),升级成本低到飞起~
团队成员对选项式更熟悉
如果团队里大多是“Vue2老玩家”,突然切换组合式API,学习成本高,代码review也费劲,先用选项式过渡,等大家熟悉Vue3后再逐步迁组合式,团队协作更丝滑~
小型组件,逻辑简单到不需要“聚合”
比如一个纯展示的按钮组件、静态列表组件,逻辑就几个props
和methods
,用选项式分块写更直观,没必要搞复杂的setup
函数。
快速做原型开发
想快速搭个Demo验证想法,选项式不用学ref
、reactive
这些新API,直接按Vue2的习惯写,效率拉满~
Vue3选项式API是Vue2的“继承者”,适合老项目迁移、团队习惯、简单组件开发;组合式API是“革新者”,适合复杂逻辑聚合、TS友好、高复用场景,没有绝对的好坏,只有合不合适~要是你现在维护老项目,或者团队还没准备好换写法,选项式完全能打;要是新项目、复杂逻辑、重度TS,组合式更香~下次写代码前,先想清楚需求和团队情况,选对API,开发效率直接起飞!
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。