Vue3 Composition 到底好在哪?怎么用更顺手?
很多同学从 Vue2 转到 Vue3 后,对 Composition API 又爱又怕:爱它能解决旧写法的痛点,怕自己没摸清套路写得更乱,今天用问答形式把 Composition 的关键问题掰碎了讲,看完你对它的理解至少深一层~
Composition API 是为了解决 Options API 的哪些痛点?
Vue2 用 Options API(把代码拆到 data、methods、computed 这些选项里)写组件时,中小项目还能应付,但项目一复杂就暴露问题:
- 逻辑复用难:想把“获取用户信息 + 表单验证”这类逻辑复用,只能用
mixin,但多个mixin会导致命名冲突(比如不同mixin都定义了handleSubmit),而且组件里突然多了一堆方法,根本分不清哪个mixin提供的(这叫“命名空间污染”)。 - 代码组织乱:一个组件同时处理“用户信息、订单列表、弹窗逻辑”时,相关代码被拆到
data、methods、computed里,要看某块逻辑得来回翻代码,就像“把完整的拼图拆得七零八落”。 - TS 支持弱:Options API 里的
this指向不明确,给 TypeScript 类型推导使绊子,写类型定义特别费劲。
Composition API 就是冲着这些痛点来的:把逻辑按功能聚合,而非按选项拆分,还让逻辑复用更干净、对 TypeScript 友好度拉满。
用 Composition 组织代码,和之前有啥不一样?
核心变化是从“按选项分类”变成“按功能聚合”,举个例子:做一个“用户信息编辑 + 订单列表”的页面,用 Options API 得这么写:
export default {
data() { return { user: {}, orders: [] } },
methods: {
fetchUser() {},
updateUser() {},
fetchOrders() {}
},
computed: { userFullName() {} }
}
代码分散在不同选项里,找“用户相关逻辑”得跳着看,换成 Composition API(用 <script setup> 语法更简洁):
<script setup>
import { ref, computed } from 'vue'
// 把“用户逻辑”聚合成一个功能块
const user = ref({})
const userFullName = computed(() => `${user.value.firstName} ${user.value.lastName}`)
function fetchUser() { /* 请求用户信息 */ }
function updateUser() { /* 提交用户修改 */ }
// 把“订单逻辑”聚合成另一个功能块
const orders = ref([])
function fetchOrders() { /* 请求订单列表 */ }
</script>
如果项目里有多个组件需要“用户逻辑”,还能把这堆代码抽到 自定义 Hook(useUser.js) 里,组件里一行 import { useUser } from './useUser' 就能复用,逻辑来源清晰,再也不怕命名冲突~
ref、reactive 这些响应式 API 怎么选?
Vue3 响应式靠 ref 和 reactive 这俩“基石”,但用法有讲究:
- ref:用来包基本类型(字符串、数字、布尔)或者对象/数组,特点是“需要
.value访问”(模板里自动解包,不用写.value)。const count = ref(0),修改时要count.value++。 - reactive:用来包对象/数组,直接代理整个对象,修改时不用
.value,但有个坑:如果给reactive变量重新赋值(user = { name: '新名字' }),会直接切断响应式!所以更推荐用ref包对象(const user = ref({})),修改属性user.value.name = '新名字'能保留响应式。
简单总结:
- 基本类型选
ref; - 对象/数组优先用
ref(避免reactive赋值丢响应式的坑); - 要是想直接操作对象属性,且确定不会整对象替换,用
reactive也没问题。
computed(计算属性)和 watch(监听)也得跟上:
computed跟 Options API 里的差不多,但要手动导入,写成const 全名 = computed(() => 逻辑);watch更灵活了,可以同时监听多个数据源,watch([count, user], ([newCount, newUser]) => { /* 数据变化后执行逻辑 */ })。
自定义 Hook 为啥能让逻辑复用更爽?
自定义 Hook 是 Composition 逻辑复用的“灵魂”,本质是把一段逻辑(数据 + 方法)封装成函数,让其他组件能直接拿走去用。
举个实际场景:很多组件需要“发起异步请求 + 加载状态 + 错误处理”,用自定义 Hook useFetch.js 封装:
import { ref, onMounted } from 'vue'
export function useFetch(url) {
const data = ref(null)
const loading = ref(true)
const error = ref(null)
onMounted(async () => {
try {
const res = await fetch(url)
data.value = await res.json()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
})
return { data, loading, error }
}
其他组件想用,直接导入:
<script setup>
import { useFetch } from './useFetch'
const { data, loading, error } = useFetch('https://api.example.com/data')
</script>
对比 mixin 有三大优势:
- 来源清晰:组件里能清楚看到
useFetch提供了哪些数据和方法,mixin则是“暗箱操作”; - 避免冲突:Hook 里的变量是局部的,不会和组件或其他 Hook 重名;
- 灵活传参:给
useFetch传不同的url,就能复用一套逻辑请求不同接口,mixin做不到这么灵活。
旧项目想切 Composition,怎么平稳过渡?
不用一刀切!分三步走:
- 新功能用 Composition:新增组件或功能时,直接用
<script setup>+ Composition API 写,旧代码保留 Options API,Vue3 完全兼容; - 逐步重构旧组件:把旧组件的
data、methods逻辑,慢慢搬到setup里,比如先把一个方法抽到setup,用ref代替data里的变量,再逐步迁移其他逻辑; - 复用逻辑抽成 Hook:遇到重复逻辑(比如弹窗管理、权限判断),抽成自定义 Hook,新旧组件都能复用,减少重复代码。
举个重构小例子,旧组件里的计数器:
// 旧写法(Options API)
export default {
data() { return { count: 0 } },
methods: { increment() { this.count++ } }
}
改成 Composition:
// 新写法(Composition API)
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() { count.value++ }
</script>
一步步来,风险低还能积累经验~
和 Pinia 结合时,Composition 能玩出啥新花样?
Pinia 是 Vue 官方推荐的状态管理工具,和 Composition API 天生一对,以前 Vuex 写模块很繁琐,Pinia 用 Composition 思路定义 Store 更简单:
// store/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
const user = ref({ name: '张三' })
const fullName = computed(() => user.value.name + '(别名)')
function updateName(newName) { user.value.name = newName }
return { user, fullName, updateName }
})
这种“setup 语法”定义 Store,和组件里用 Composition API 几乎一样,学习成本低,Store 里的逻辑还能拆成自定义 Hook,比如把“用户权限判断”抽到 usePermission.js,Store 里导入复用,逻辑分层更清晰。
实际开发里,Composition 有哪些容易踩的坑?
新手最容易栽的几个点:
- ref 的 .value 忘记写:在
setup函数里修改ref变量时,必须写.value,count.value++,但模板里不用写(Vue 自动帮你解包); - reactive 赋值丢响应式:如果给
reactive变量整个替换(user = { name: '新名字' }),原来的响应式会失效,改成用ref包对象更安全; - 生命周期钩子导错:Vue3 里生命周期钩子要从
vue导入,onMounted代替mounted,忘记导入会报错; - this 指向丢了:Composition API 里没有
this(<script setup>是无this模式),如果硬要在方法里用this,得换回 Options API 写法,否则会踩坑; - 自定义 Hook 里的响应式丢失:Hook 里返回的是普通变量(不是
ref/reactive),组件里拿不到响应式更新,Hook 里要返回ref/reactive包装后的值。
最后补一句:Composition API 不是要完全替代 Options API,而是给复杂场景提供更优解,小项目用 Options 写得快,中大型项目用 Composition 更易维护,关键是理解它“聚合逻辑、灵活复用”的设计思想,用对场景才是王道~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


