最近几年但凡聊到Vue,几乎绕不开一个话题:Vue3的组合式API到底比Vue2的选项式API强在哪。我在做技术咨询和代码评审时经常遇到两类朋友,一类是Vue2老用户,看到setup和ref就打心底抵触;另一类是刚入门的新手,看官方文档在两种API之间反复横跳,越看越懵。其实只要把两者背后的设计意图、组织逻辑和适用场景搞清楚,这个问题并没有想象中那么玄。今天这篇就把选项式API和组合式API从定义、原理、代码组织、复用方式、迁移实操到面试高频题完整拆一遍,无论你是在维护老项目、准备跳槽面试,还是决定直接上手Vue3,都能从中拿到一套清晰可落地的判断依据。
1. 为什么会有两套API:选项式的“结构容器”遇到了什么问题
1.1 选项式API的设计初心:让不同类型代码各归其位
Vue2的选项式API在设计上有一个非常朴素的目标:让写组件像填表格一样简单。data负责数据,methods负责方法,computed负责计算属性,watch负责侦听变化,mounted、created之类的生命周期选项负责特定时机执行逻辑。任何一个稍微接触过Vue的人,都能在短时间内看懂一个组件的静态结构,因为代码被强制按照“类型”归位,想找数据去data,想找方法去methods,规律清楚得很。
这种设计在页面规模小的时候确实好用。一个简单的计数器组件,data里放count,methods里放increment,模板里一个按钮绑定事件,五分钟就能写出来。但随着项目由小变大,问题就慢慢浮出水面:同一段业务逻辑的代码,会被拆散到多个选项里去。
拿一个典型的中后台用户管理列表来说,搜索、筛选、分页、列表加载本质上属于同一个完整业务闭环。但在选项式API中会出现什么局面呢?data里躺着keyword、page、pageSize、total、userList、loading六个状态,computed里放一个filteredList做前端过滤,watch里盯着keyword变化触发接口请求,methods里塞下fetchList、handleSearch、handlePageChange等五六个方法,created里还要再调一次fetchList完成首次加载。搜索这个功能本身,散落在至少四个选项里。
一个真实开发场景更让人抓狂:接手一个老项目,需求是给某个列表页增加“按创建时间倒序”的筛选条件。光是找到这个列表功能涉及的所有代码,就得在data、methods、computed、watch、created之间来回跳转,等你好不容易理清了修改点,改完之后又担心影响了另一条隐藏的业务分支。这种隐形的阅读成本,项目越大越明显,到后期基本是靠经验和搜索工具在维持维护效率。
1.2 组合式API的破局思路:按“功能块”而不是“选项类型”组织代码
Vue3的组合式API做了一个非常大胆的调整:不再强制要求你按代码类型分抽屉,而是允许你按“业务功能”把相关代码聚在一起。setup函数是组件的入口,在组件创建之前执行,你可以在里面用ref、reactive声明响应式状态,用computed定义计算属性,用watch、watchEffect处理侦听逻辑,用onMounted、onBeforeUnmount这些函数式API挂载生命周期回调。
组合式API真正改变的不是语言能力,而是代码摆放方式。打个比方,选项式API像是按物品类别整理房间,所有衣服塞一个衣柜,所有书塞一个书架;组合式API则是按使用场景整理,出门穿的那套衣服和通勤要用的电脑放在同一个包里。两种方式都能把房间收拾干净,但在“日常取用”和“应对突发需求”的效率上差别很大。
组合式API还有一个更关键的隐藏价值,它天然倒逼你考虑“拆分”。setup函数如果塞得又长又乱,写代码的人自己都受不了,这种不适感会促使你主动把一坨逻辑拆成use开头的组合式函数。而选项式API因为结构固定,很多时候你即使知道这里需要复用,也只能往mixins里塞,越塞越乱。
1.3 一张对比表看清宏观差异
| 对比维度 | Vue2 选项式API | Vue3 组合式API |
|---|---|---|
| 代码组织方式 | 按选项类型分组(data/methods/computed/watch) | 按业务逻辑功能分组 |
| 响应式实现 | Object.defineProperty | Proxy |
| this指向 | 组件实例,随处可用 | setup中为undefined,需要显式传参 |
| 逻辑复用方式 | mixins、高阶组件、render函数 | composables(组合式函数) |
| 生命周期写法 | 选项名(created、mounted等) | onXxx函数(onMounted、onBeforeUnmount等) |
| 路由/状态访问 | this.$route、this.$store | useRoute、useRouter、useStore |
| 类型推导友好度 | 较弱,this上属性推导困难 | 强,变量和返回值类型明确 |
还有一个底层响应式的差别必须单独提:
| 响应式能力 | Vue2 | Vue3 |
|---|---|---|
| 新增对象属性 | 无法自动响应,需要Vue.set | Proxy天然支持 |
| 删除对象属性 | 无法自动响应 | 天然支持 |
| 数组下标赋值 | 无法自动响应,需要用splice或整体替换 | 天然支持 |
| 深层嵌套监听 | 递归遍历,性能开销大 | 惰性代理,读取时才深层次处理 |
Vue2的响应式是数据层面的补丁式实现,Vue3的响应式是语言层面的能力升级。这一点在后文的实际操作中会有非常直观的感受。
2. 核心差异逐个拆解:状态、生命周期、复用逻辑
2.1 状态声明与响应式原理的底层对比
Vue2选项式API中,data必须是一个函数,返回一个对象,对象里的每个属性都会被Object.defineProperty改造成带有getter和setter的“响应式属性”。这种方案有两个先天的坑,我在实际项目中全踩过。
第一个坑是新增属性不响应。接口返回的数据结构经常比前端预期的多一层,比如用户对象刚开始没有age字段,后端在某个版本后突然返回了age,直接给this.user.age = 18赋值,视图不会有任何变化。你必须用this.$set(this.user, 'age', 18)才能让它响应。第二个坑是数组下标修改不响应,this.list[0] = { name: '张三' }之后,页面纹丝不动,得用this.$set或splice才能触发更新。
Vue3换成了Proxy代理整个对象,上面两个痛点直接被消灭了。直接给对象加新字段,直接按索引换数组元素,页面都会正常更新。写惯了Vue2的人第一次在Vue3里体验到这个,往往会有一种“还有这种好事”的意外感。
但是,Proxy同样有它的注意事项。Proxy是在对象读取和设置时进行拦截的,如果把一个超大、嵌套极深的普通对象扔进reactive,首次深层代理也会带来可感知的初始化开销。更实际的问题是,我见过有人把整个接口返回的response对象直接reactive,然后再把里面的data字段拆出来用。这种做法不仅多余,还容易把不需要响应式的数据拖进响应式系统里,白白消耗性能。我的建议是:要响应式的数据,明确用ref或reactive声明;不需要响应式的静态数据,放在普通const对象里即可。
2.2 setup中的this为什么是undefined
打开Vue3写完第一个setup函数,十个人里有八个人会下意识写一个this.xxx然后报错。Vue2时代的组件逻辑几乎都依赖this:this.data、this.methods、this.$route、this.$store,this是那个把一切粘合在一起的胶水。而Vue3的setup在组件实例还没有创建完成时就会执行,this压根不存在,访问就是undefined。
这个改动看起来是限制,实际是刻意为之。Vue团队希望开发者不再依赖一个隐式的、全局的、无法被类型系统很好推导的this,而是把状态和方法以变量形式显式声明、显式返回。这样代码逻辑从“组件实例里的神秘属性”变成了“函数作用域内看得见的变量”,对TypeScript推导尤其友好。
我在迁移时的体会是,刚开始很不习惯,写什么都觉得要return一堆东西很啰嗦。但当代码规模上来之后,反而觉得安心:每一个变量从哪里来、到哪里去,作用域清清楚楚,不会出现Vue2里“在模板里看到一个方法,不知道它是组件的还是mixin注入的”那种情况。
2.3 生命周期钩子的三种变化
生命周期是Vue2和Vue3差异最直接的部分。Vue2的生命周期选项是字符串命名的选项:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed。到了Vue3,变成了onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted这样的函数式API。
第一个变化是created和beforeCreate被setup替代。Vue2里created常用于发请求、初始化数据、监听事件,Vue3里这些直接写在setup函数中即可。第二个变化是beforeDestroy和destroyed更名为onBeforeUnmount和onUnmounted。不只是改名,语义也从“销毁”变成了“卸载”,强调组件实例从组件树上被移除的过程。
第三个变化更隐蔽:组合式API的生命周期函数必须在setup执行期间同步调用。也就是说,你不能在setTimeout回调里调用onMounted,也不能在composable的异步逻辑里调用它。因为Vue需要把这些回调注册到当前正在创建的组件实例上,如果调用时机错了,生命周期钩子会失效或告警。
老项目迁移时最容易犯的错误,就是只把created里的初始化逻辑搬到setup里,而忘记把mounted里的DOM操作换成onMounted。正确的写法是:
// Vue2 export default { mounted() { this.$refs.chart && this.initChart() }, beforeDestroy() { this.chart && this.chart.dispose() } } // Vue3 import { ref, onMounted, onBeforeUnmount } from 'vue' export default { setup() { const chartEl = ref(null) let chart = null onMounted(() => { chart = initChart(chartEl.value) }) onBeforeUnmount(() => { chart && chart.dispose() }) return { chartEl } } }2.4 计算属性与侦听器:函数化的computed和watch
计算属性和侦听器是Vue开发者每天都会用到的东西,它们的变化同样明显。Vue2中computed是对象,每一项是计算函数;watch也是对象,每一项是侦听配置。Vue3中computed变成了一个函数,传入一个getter,返回一个Ref对象;watch也变成了显式函数,第一个参数是侦听源,第二个参数是回调,第三个参数是配置。
// Vue2 选项式 export default { data() { return { keyword: '', userList: [] } }, computed: { filteredList() { return this.userList.filter(item => item.name.includes(this.keyword)) } }, watch: { keyword(newVal) { this.handleSearch(newVal) } } } // Vue3 组合式 import { ref, computed, watch } from 'vue' export default { setup() { const keyword = ref('') const userList = ref([]) const filteredList = computed(() => userList.value.filter(item => item.name.includes(keyword.value))) watch(keyword, (newVal) => handleSearch(newVal)) return { keyword, userList, filteredList } } }watch还有一个小兄弟叫watchEffect,它不需要显式指定侦听源,只要函数内部用到了响应式数据,它就会自动跟踪这些数据并在变化时重新执行。这个API特别适合“副作用型”逻辑,比如自动保存草稿、同步URL参数、写入本地缓存。我在实际项目中常拿watchEffect同步页面标题和面包屑,代码量比watch加一堆重复参数清爽得多。
组合式API里computed的常见坑是忘记.value。computed返回的是Ref对象,在模板里Vue会自动解包,写{{ filteredList }}没问题;但在setup或普通JS逻辑里,必须写filteredList.value。新手第一次在代码里拿computed返回值当对象用,通常会得到一个undefined或报错。
3. 真正的分水岭:代码复用与组件逻辑组织
3.1 mixins为什么被composables取代
Vue2实现逻辑复用主要靠mixins。一个mixin就是一个包含data、methods、computed的选项对象,组件通过mixins数组引入,mixin里的所有选项会被合并进组件。说白了,mixin就是一份“无感注入”的代码拷贝。
用过mixins做大型项目的人,一定都体会过这几个痛点。
第一是命名冲突不可见。两个mixin里都定义了fetchList,后引入的会覆盖先引入的,模板里调用的到底是哪一个,完全没有提示。第二是隐式耦合。mixin里的方法通过this访问组件数据,它依赖的this.xxx由谁提供、何时提供,全靠约定,代码一多基本靠猜。第三是来源不透明。模板里用了一个search方法,它可能来自组件本身,也可能来自任意一个mixin,只能翻文件找。
组合式函数composables把问题一次性解决。它就是一个普通函数,以use开头,内部可以用ref、computed、watch、生命周期函数,最后显式return暴露的内容。
// 一个标准的composable:usePagedList import { ref, watch } from 'vue' export function usePagedList(fetcher) { const list = ref([]) const loading = ref(false) const error = ref(null) const page = ref(1) const pageSize = ref(20) const total = ref(0) async function load() { loading.value = true error.value = null try { const res = await fetcher({ page: page.value, pageSize: pageSize.value }) list.value = res.list total.value = res.total } catch (e) { error.value = e } finally { loading.value = false } } function reset() { page.value = 1 total.value = 0 load() } watch([page, pageSize], load) return { list, loading, error, page, pageSize, total, reset, load } }组件里是这样使用的:
import { usePagedList } from '@/composables/usePagedList' import { getUserList } from '@/api/user' export default { setup() { const { list, loading, page, pageSize, total, reset, load } = usePagedList(getUserList) return { list, loading, page, pageSize, total, reset, load } } }对比mixin最大的优势在于:数据来源完全显式。list、total、load都是usePagedList返回的,模板里出现的方法和变量都找得到出处,重构的时候删掉一个composable调用,相关的状态和方法全部消失,没有任何隐藏的副作用。而且composable是函数,天然支持参数。比如usePagedList可以接收不同的fetcher,一个列表页换接口只要换参数,不需要改逻辑。
3.2 一个真实业务场景:搜索、分页、列表加载的两种写法
为了更直观地说明差异,我用一个完整的“用户列表搜索分页”组件来对比。
Vue2选项式的完整结构:
export default { data() { return { keyword: '', page: 1, pageSize: 20, total: 0, list: [], loading: false } }, computed: { totalPages() { return Math.ceil(this.total / this.pageSize) } }, created() { this.fetchList() }, watch: { keyword() { this.page = 1 this.fetchList() }, page() { this.fetchList() } }, methods: { async fetchList() { this.loading = true try { const res = await api.getUserList({ keyword: this.keyword, page: this.page, pageSize: this.pageSize }) this.list = res.list this.total = res.total } finally { this.loading = false } }, handleSearch() { this.page = 1 this.fetchList() }, handlePageChange(page) { this.page = page } } }这已经是写得比较规整的选项式组件了,但依然能看出来问题:keyword、page、list、loading这些状态在data里,fetchList在methods里,totalPages在computed里,逻辑被拆成四块,维护时需要在四块之间来回跳。
Vue3组合式API可以直接把这一整个功能写成一个usePagedList composable(正如上一节展示的),组件里只需要调用它、绑定模板。如果把完整的搜索、分页逻辑都放进composable,组件代码的“业务噪音”会降到极低:
<script setup> import { ref } from 'vue' import { usePagedList } from '@/composables/usePagedList' import { getUserList } from '@/api/user' const keyword = ref('') const { list, loading, page, total } = usePagedList((params) => getUserList({ ...params, keyword: keyword.value })) </script> <template> <input v-model="keyword" /> <div v-loading="loading"> <el-table :data="list" /> <el-pagination v-model:current-page="page" :total="total" /> </div> </template>注意这里的usePagedList被调用时传入了keyword,每次keyword变化,传给fetcher的参数会自动带上新值,分页逻辑完全在一个composable内部被管理起来。这个模式在多个列表页复用的时候,价值成倍放大。
3.3 配合路由、状态管理和动态路由的实际差异
Vue2选项式里获取路由参数、跳转页面、读取全局状态都依赖this:this.$route.params.id、this.$router.push、this.$store.state.userInfo。Vue3组合式API提供了对应的composition风格函数:useRoute、useRouter、useStore。
以动态路由为例。中后台项目经常需要根据用户角色动态添加路由,Vue2时代我写权限模块时,逻辑都挤在路由配置文件里,用beforeEach守卫里判断用户信息、遍历动态路由配置、最后router.addRoutes。一旦项目里有多个角色、多套权限、多级菜单,这段代码会膨胀到难以维护。
在Vue3组合式API中,整个权限路由逻辑可以收口成一个composable,比如usePermissionRoutes,内部使用ref维护路由表,使用router.addRoute动态添加,使用watch或watchEffect监听用户角色变化,返回给页面一个菜单数据源。页面组件只关心“我能访问什么”、“菜单从哪来”,因为显式调用了usePermissionRoutes,谁维护路由、怎么过滤,一目了然。
从代码复用角度看,这又是组合式API对选项式API的一次明显胜出。Vue2也能用函数封装路由逻辑,但那种封装和组件自身的options强耦合,用起来始终绕不开this。Vue3的useXxx让路由、状态管理、请求库、UI库全部回归到了“普通函数”的形态。
3.4 面试题里的对比考察点
Vue相关的面试题中,“选项式API和组合式API的区别”几乎成了必考题。围绕这个核心考点,面试官通常会从以下角度深挖:
- 为什么Vue3要用Proxy替换Object.defineProperty:因为defineProperty只能拦截已有属性,对象新增属性和数组索引操作无法响应;Proxy可以拦截整个对象,包括属性添加、删除和数组原生操作。
- setup为什么没有this:setup在组件实例创建前执行,此时没有绑定组件实例。这个设计还让组合式API不依赖this,便于类型推导和逻辑复用。
- 组合式API有什么缺点吗:对小组件来说会更加啰嗦;如果不会拆composable,setup函数会变成臃肿的“大杂烩”。这个答案在面试里说,很加分。
- 选项式API和组合式API能混用吗:Vue3和Vue2.7都可以混用,但规范化的团队一般不推荐在同一个组件里混合,会降低可维护性。
4. 迁移与实战:从选项式到组合式怎么落地
4.1 老项目也可以用上组合式API
很多Vue2项目的团队不敢直接升Vue3,怕的是生态组件、构建工具、历史代码改造量太大。但如果在Vue2项目里提前尝鲜组合式API,是有路的。
第一条路是升级到Vue2.7。Vue2.7是官方为过渡准备的版本,内置了组合式API支持,不需要额外安装任何插件。data和setup可以同时存在,组件选项和setup函数可以混用,模板行为与Vue3基本对齐。第二条路是停留在Vue2.6及以下,通过安装@vue/composition-api插件获得组合式API。这个插件在大多数API行为上与Vue3一致,但部分边界功能(例如Fragment、部分模板特性)会有差异。
我在实际项目中推荐的是:如果团队短期内无法升Vue3,至少先升到Vue2.7,把组合式API用起来。新增页面和复杂逻辑用composable写,老页面先不动,逐步消化。这样既降低了重构风险,又提前培养了团队对组合式API的熟悉度。
4.2 一个老页面的渐进式改写流程
以典型的“人力资源后台管理”这类项目为例,项目里通常有大量列表页、表单页和详情页。从选项式改成组合式时,我建议不要一上来就重写整个页面,而是走一套渐进式流程。
第一步,把页面里最独立的一段逻辑挑出来。比如一个员工列表的搜索分页,先只把这段逻辑改写成一个composable,返回list、loading、page等状态和load、reset方法。
第二步,在组件里混用。Vue2.7或Vue3都允许选项式和组合式同时存在于一个组件中,组件模板暂时不变,data里和methods里已经迁移的部分可以先不删,等测试通过后再清理。
第三步,替换路由和全局状态的访问。v-text不用管,但脚本里的this.$route要改成useRoute(),this.$store要改成useStore(),这一步改动虽然机械,但数量多,容易漏。
第四步,清理旧代码。把data里已经被setup中ref替代的字段删掉,把methods里已经被composable函数替代的方法删掉,同时把created里的初始化逻辑并入setup或onMounted。
第五步,反复测试。组合式API重构最常见的风险是生命周期时序变化。Vue2的created在所有数据初始化后立即执行,Vue3的setup在实例创建前执行,两者时序并不完全等价。原来created里访问this.$el的代码,挪到setup后会拿到null,必须改成onMounted。
4.3 和SpringBoot打包、项目交付等场景结合起来看
热搜词里有“vue打包放进springboot中”和“vue项目源码怎么发给别人”,这些和选Vue2还是Vue3也有一定关系。
Vue项目打包之后,dist目录里的静态文件可以直接放进SpringBoot的static目录。但这里有几个配置细节,Vue2和Vue3都一样需要处理。如果使用vue-router的history模式,刷新某个子路径时SpringBoot会真的去找这个路径的后端接口,返回404。解决办法通常是在SpringBoot里配置一个forward转发,把未知路径统一转发到index.html。
Vue3配合Vite使用后,构建产物的base路径默认是/,如果你把项目部署在一个子路径下,需要在vite.config.js中设置base,否则静态资源全部404。这个配置在Vue CLI时代是publicPath,很多从Vue2切Vue3的人会漏掉。
再说到“项目源码怎么发给别人”,不管Vue2还是Vue3,node_modules都不应该发,只需要发项目源码、package.json、package-lock.json(或yarn.lock、pnpm-lock.yaml)。别人拿到后先跑npm install,不同机器上因为有锁文件版本一致,不会出现环境依赖差异。Vue3+Vite的项目对Node版本有要求,官方要求Node 16以上,发源码给对方之前最好在package.json里engines字段里标注Node大版本,节省双方排查环境的时间。
4.4 前端新人的学习路线建议
热搜里有不少“vue入门”、“vue快速学习路线”相关词。我给新人的建议很明确,直接学Vue3,但不要跳过选项式API的理解。
原因很简单,当前企业存量项目里Vue2的比例仍然很高,面试官一定会问“Vue2和Vue3有什么区别”、“你会不会写Vue2”。你如果完全不会选项式,连别人的老代码都看不懂。反过来讲,直接学Vue2再转Vue3也是绕了一圈,因为Vue3才是未来方向。
推荐的学习顺序是先把模板语法、指令、组件通信这些基础掌握住,然后进入组合式API,重点把ref、reactive、computed、watch、composable这五件事学透。框架本身之外,vue-router和Pinia也是必学项。上手项目可以用Vite搭一个简单中后台,把动态路由和权限控制做一遍,基本就能覆盖面试里的高频考点。
5. 常见问题与避坑实录
5.1 ref还是reactive,到底怎么选
这是组合式API里出现频率最高的问题。我的项目经验是:单个基本类型值、需要整体替换的引用、列表数据用ref;结构化、深层嵌套、需要大量直接访问属性的对象用reactive。
比如表单对象用reactive很舒服:
const form = reactive({ name: '', age: 25, address: { city: '', district: '' } })而一个用户列表,如果用reactive,就要写成list.value?不用,reactive访问直接list就能拿到数组,但整体替换时需要写成Object.assign或重新赋值整个属性。相比之下ref支持整体替换userList.value = newList,语义更直接。
还有一个偏门但真实的坑:对reactive对象进行解构,会丢失响应式。
const { name, age } = form // 丢失响应式解决方式是使用toRefs:
const { name, age } = toRefs(form)解构出来的name和age变成了ref,在模板里正常使用,在代码逻辑里要注意.value。
5.2 setup中拿不到this,怎么访问组件实例
setup里访问this是undefined,如果确实需要组件实例,官方提供了getCurrentInstance()。但我必须提醒一句:这个API在文档中标记为“内部用途”,开发者不应该把它当成稳定的公共API来依赖。在组件库源码里可以见到它的身影,在业务代码里尽量别用。
绝大多数想要this的场景,都有更安全的替代手段:想访问props,用setup的第一个参数;想触发父组件事件,用setup的第二个参数里的emit;想共享组件间的状态,用provide/inject,或提升到Pinia;想拿DOM元素,用ref绑定DOM,比如<div ref="myDiv">,在setup里声明const myDiv = ref(null),onMounted后读取myDiv.value。
5.3 组合式API一定会让代码更少吗
不一定。简单的展示型组件,用选项式API写起来甚至更紧凑,data和methods两栏就能搞定。组合式API带来的收益要从“中大型组件维护复杂度”和“可复用逻辑的抽取成本”两个维度来评估。
我在代码评审中见过最糟糕的Vue3写法,是把所有数据和方法一股脑塞进setup,然后return一大串变量,既不拆composable,也不做逻辑分区。结果setup比原来的data、methods加起来还要长,模板里绑定了一堆来源不明的变量。这种改法不是组合式API的功劳,是使用方式出了问题。
组合式API的哲学是逻辑聚合和显式依赖,不是“把this.去掉换成return”。如果你写setup开始觉得混乱,第一步不是继续堆变量,而是停下来想想哪些逻辑可以抽成一个use函数。逻辑越复杂,抽composable收益越大。
5.4 面试高频对比速查表
| 考察点 | Vue2选项式 | Vue3组合式 |
|---|---|---|
| 状态声明方式 | data(): 返回对象 | ref() / reactive() |
| 创建阶段逻辑 | created / beforeCreate | setup() |
| 挂载阶段逻辑 | mounted | onMounted |
| 卸载阶段逻辑 | beforeDestroy / destroyed | onBeforeUnmount / onUnmounted |
| 计算属性 | computed: {} | computed(() => {}) |
| 侦听器 | watch: {} | watch() / watchEffect() |
| 路由访问 | this.$route / this.$router | useRoute() / useRouter() |
| 全局状态 | this.$store / Vuex | useStore() / Pinia |
| 逻辑复用 | 普通函数 + mixins | composables(useXxx) |
| 类型推导 | this相关推导弱 | 函数返回值推导强 |
把这个表背熟并不难,关键是理解每一行背后的取舍。面试官问到“你更喜欢哪套API”,不用回避,直接说自己在实际项目中更常用组合式API,因为可复用性和代码可读性更好,但要补充一句“复杂度不高的组件用选项式也不会有问题”。这个回答既显得你有实践经验,又没把话说死。
我个人在两套API之间来回切换的一个体会是:选项式API让我入门Vue时觉得“框架真简单”,但组合式API让我面对复杂业务时觉得“代码真可控”。如果你现在还守在Vue2老项目里,不必急着否定组合式API,先挑一个小组件用setup重写一遍,感受一下同一个状态从data挪到ref之后,代码组织发生了什么变化。如果你刚开始学Vue3,也别把选项式API当成必须淘汰的旧物,它是理解Vue设计演进的重要参照。最后再分享一个小技巧:想快速熟悉组合式API,把项目升级到Vue2.7之后,每天抽一个小组件用setup重写,连写一周,你对composable的理解会比读十篇文档都深刻。