3. computed计算属性:从OptionsAPI的computed到组合式API的computed函数
计算属性,说白了就是「有缓存的计算结果」。
我记得刚接触Vue时,最让我眼前一亮的就是computed。你想想看,不用手动管理依赖,不用操心什么时候重新计算,它自己就搞定了。但在OptionsAPI时代,computed的写法其实有点「死板」——你只能声明一个名字,然后给它一个getter。到了组合式API,computed变成了一个函数,灵活度直接拉满。
这一节,我们就来聊聊computed的迁移。我会从最简单的只读计算属性讲起,再到可写的计算属性,最后分享一些我在项目中踩过的坑。
3.1 从OptionsAPI到组合式API:基础迁移
先看一个最典型的场景:我们有一个用户对象,需要展示「全名」。
OptionsAPI写法:
export default {
data() {
return {
firstName: '张',
lastName: '三'
}
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
}
}
组合式API写法:
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value
})
嗯,这里要注意一个细节:在组合式API中,computed接收的是一个函数,而不是一个对象。这个函数就是getter,它必须返回一个值。
我个人习惯把computed放在ref后面,这样代码读起来很顺畅——先声明数据源,再声明派生数据。你想想看,这不就是数据流的自然顺序吗?
3.2 只读计算属性:默认行为
上面那个例子,其实就是只读计算属性。为什么叫「只读」?因为你不能直接修改fullName的值。
试试看:
fullName.value = '李四' // ❌ 报错!Computed is readonly
为什么会这样?因为computed默认只暴露getter,没有setter。它的值完全由依赖的响应式数据决定。你强行赋值,Vue会告诉你「不行,这是只读的」。
我在项目中遇到过一位同事,他试图在组件里直接修改计算属性的值来实现「双向绑定」,结果折腾了半天没搞明白。其实,计算属性的本质是派生状态,它的值应该由源数据推导而来,而不是被手动设置。
3.3 可写计算属性:什么时候需要?
那有没有需要「写」计算属性的场景呢?有,而且不少。
最常见的场景就是v-model绑定一个需要转换的值。比如,你有一个价格输入框,用户输入的是「元」,但后端存储的是「分」。
看代码:
import { ref, computed } from 'vue'
const priceInFen = ref(100) // 后端存储的是分
// 可写计算属性
const priceInYuan = computed({
// getter:从分转换为元
get() {
return priceInFen.value / 100
},
// setter:从元转换为分
set(val) {
priceInFen.value = val * 100
}
})
现在,你可以直接在模板里用v-model绑定priceInYuan:
<input v-model="priceInYuan" type="number" />
用户输入10,priceInFen自动变成1000。用户看到的是元,后端存的是分,完美解耦。
3.4 可写计算属性的避坑指南
我曾经在一个电商项目中,用可写计算属性来处理购物车的「数量」和「小计」。结果出了个bug:用户修改数量后,小计没有立即更新。
排查了半天,发现是setter里写错了:
// ❌ 错误写法
const subtotal = computed({
get() {
return quantity.value * price.value
},
set(val) {
// 这里只修改了quantity,但price没变
quantity.value = val / price.value
}
})
问题出在哪?如果price.value是0,直接报错。而且这种写法逻辑上也不对——小计应该是「数量×单价」,你修改小计时,到底是想改数量还是改单价?
我的建议是: 可写计算属性的setter,最好只修改一个依赖源。如果涉及多个依赖,很容易出现逻辑混乱。
| 场景 | 推荐做法 | 说明 |
|---|---|---|
| 单位转换(元↔分) | 可写计算属性 | setter只修改一个源数据,逻辑清晰 |
| 全名(姓+名) | 只读计算属性 | 修改全名时,你不知道该改姓还是改名 |
| 购物车小计 | 只读计算属性 | 小计是派生值,不应该被手动设置 |
| 表单双向绑定+格式化 | 可写计算属性 | getter格式化显示,setter解析存储 |
3.5 组合式API中computed的额外优势
除了读写控制,组合式API的computed还有一个隐藏技能——可以脱离组件使用。
在OptionsAPI里,computed必须写在组件选项里。但在组合式API中,你可以把computed定义在独立的文件里,然后到处复用:
// utils/usePrice.js
import { ref, computed } from 'vue'
export function usePrice(initialFen = 0) {
const priceInFen = ref(initialFen)
const priceInYuan = computed({
get: () => priceInFen.value / 100,
set: (val) => { priceInFen.value = val * 100 }
})
return {
priceInFen,
priceInYuan
}
}
然后在任何组件里:
const { priceInFen, priceInYuan } = usePrice(500)
你看,计算属性变成了一个「可组合的函数」。这种复用方式,在OptionsAPI里根本做不到。
3.6 总结:迁移要点
最后,我整理了一个对照表,方便你快速迁移:
| 功能 | OptionsAPI | 组合式API |
|---|---|---|
| 只读计算属性 | computed: { fullName() { ... } } |
const fullName = computed(() => ...) |
| 可写计算属性 | computed: { fullName: { get, set } } |
const fullName = computed({ get, set }) |
| 依赖追踪 | 自动(通过this) | 自动(通过响应式引用) |
| 复用性 | 只能在组件内 | 可以提取到独立函数 |
说白了,computed的迁移是最平滑的。你只需要记住:OptionsAPI里用对象声明,组合式API里用函数调用。至于只读和可写,区别就在于传入的是函数还是对象。
下一节,我们会聊聊watch——那个比computed更「主动」的家伙。准备好了吗?