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前端网发表,如需转载,请注明页面地址。
code前端网



发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。