Vue3里computed的getter和setter咋用?这些细节得搞懂!
很多刚学Vue3的同学,对computed里的getter和setter总是一知半解:默认只写个函数是getter?加setter有啥用?和methods到底咋选?今天咱们把这些问题拆碎了讲明白,还结合实际场景举例子~
computed里的getter,到底负责啥?
先想清楚:计算属性的本质是“基于其他响应式数据,动态生成新值”,而getter就是实现这个逻辑的“默认配置”——你只写一个函数的时候,Vue自动把它当成getter。
举个最简单的例子:
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 这里的computed只传了一个函数,这个函数就是getter
const fullName = computed(() => `${firstName.value}${lastName.value}`)
getter里做了这几件事:
- 依赖收集:Vue会自动跟踪
firstName和lastName这两个响应式数据,只要它们的value变化,fullName就会重新计算。 - 结果缓存:如果
firstName和lastName都没变化,哪怕你在模板里多次用{{ fullName }},Vue也不会重复执行getter函数,直接用上次的结果——这也是computed和methods最大的性能差异(后面会细讲)。
你可以把getter理解成“自动更新的动态变量”:它不是固定值,而是随依赖变化自动刷新的“计算结果”。
啥时候得给computed加setter?
默认情况下,computed只有getter,是只读的,如果直接给computed赋值,比如fullName.value = '李四',Vue会报错“Write operation failed: computed value is readonly”。
但实际开发中,经常需要“通过修改计算属性,反向更新它的依赖项”——这时候就得给computed加setter,让它变成可写的。
还是拿“全名”举例子,这次需求是:用户在输入框里输入“李-四”,自动拆分姓和名,更新firstName和lastName,这时候setter就派上用场了:
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({
// getter:负责把姓和名拼成全名
get() {
return `${firstName.value}${lastName.value}`
},
// setter:负责把全名拆分成姓和名
set(newValue) {
// 假设用户输入格式是“姓-名”,李-四”
const [f, l] = newValue.split('-')
firstName.value = f
lastName.value = l
}
})
// 模拟用户输入后赋值
fullName.value = '李-四'
// 此时firstName变成'李',lastName变成'四',fullName的getter也会自动更新为'李四'
场景延伸一下:比如表单里有“手机号(带空格格式)”和“原始手机号(纯数字)”两个字段,用户输入带空格的手机号时,你需要自动去掉空格存到原始字段里——这时候computed的setter负责“处理输入格式”,getter负责“展示带格式的内容”,特别顺手。
getter和setter结合,实际开发哪些场景能用上?
除了“姓名拆分”“手机号格式化”,还有这些常见场景:
表单数据的“显示 vs 存储”分离
比如后台返回的生日是时间戳(birthTimestamp),但界面要显示“YYYY - MM - DD”格式,这时候:
- getter:把时间戳转成日期字符串展示;
- setter:用户修改日期字符串时,转成时间戳存回
birthTimestamp。
代码大概长这样:
const birthTimestamp = ref(1672531200000) // 假设是2023 - 01 - 01的时间戳
const birthDisplay = computed({
get() {
return dayjs(birthTimestamp.value).format('YYYY - MM - DD')
},
set(val) {
birthTimestamp.value = dayjs(val).valueOf() // 转成时间戳
}
})
关联数据的双向同步
比如购物车中,“商品数量”和“商品总价”是关联的(总价 = 数量 × 单价),如果业务需要“修改总价时反向计算数量”(比如促销场景下的批量改价),就可以用computed的setter:
const price = ref(100) // 单价
const count = ref(2) // 数量
const total = computed({
get() {
return price.value * count.value
},
set(newTotal) {
count.value = Math.floor(newTotal / price.value) // 假设只保留整数数量
}
})
// 模拟修改总价
total.value = 500
// 此时count会变成5(因为500 / 100 = 5)
复杂状态的“组合与拆分”
比如用户信息有user = { name: '张三', age: 18 },但界面有个输入框要同时改“姓名”和“年龄”(比如格式是“张三|18”),这时候computed的getter负责拼接,setter负责拆分:
const user = reactive({ name: '张三', age: 18 })
const userDisplay = computed({
get() {
return `${user.name}|${user.age}`
},
set(val) {
const [name, age] = val.split('|')
user.name = name
user.age = +age // 转成数字
}
})
computed的getter和methods,性能差异有多大?
很多同学疑惑:“既然getter能做的事,methods也能做,为啥非得用computed?” 核心差异就在“缓存机制”上。
用一段代码对比感受下:
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9])
// computed的getter:只在依赖变化时重新计算
const evenList = computed(() => {
console.log('computed执行了')
return list.value.filter(num => num % 2 === 0)
})
// methods:每次调用都执行
function getEvenList() {
console.log('methods执行了')
return list.value.filter(num => num % 2 === 0)
}
然后在模板里渲染:
<template>
<!-- 情况1:用computed -->
<div>{{ evenList }}</div>
<div>{{ evenList }}</div>
<!-- 情况2:用methods -->
<div>{{ getEvenList() }}</div>
<div>{{ getEvenList() }}</div>
</template>
控制台输出会是这样:
- computed只在
list变化时执行1次,不管模板里用多少次evenList; - methods每次渲染都会执行,哪怕
list没变化,模板里用几次就执行几次。
如果list是几百条数据,或者在循环渲染(比如v - for里调用方法),methods的重复执行会让性能雪崩,而computed的缓存能极大减少计算次数——这也是Vue官方推荐“能用computed就不用methods”的原因(事件处理函数还是得用methods)。
用computed的setter,容易掉哪些“坑”里?
虽然setter很好用,但稍不注意就会踩坑,这几个常见问题得避着点:
忘记写setter,赋值时报错
前面说过,默认computed是只读的,如果代码里写了fullName.value = '李四',但没给fullName加setter,Vue会直接报错。
解决方法:只要需要给computed赋值,必须显式写setter(哪怕setter里暂时没逻辑,也得写个空函数兜底)。
setter里的依赖不是响应式的
比如你想在setter里修改一个普通变量,而不是ref/reactive包裹的响应式数据:
let firstName = '张' // 普通变量,不是响应式
const fullName = computed({
get() { return firstName + lastName.value },
set(newValue) {
firstName = newValue.split('-')[0] // 修改普通变量,界面不更新
}
})
这时候哪怕firstName变了,fullName的getter也不会重新计算,因为Vue的响应式系统“盯不住”普通变量。
解决方法:所有在computed里用的变量,必须用ref或reactive包装成响应式。
setter逻辑太复杂,引发意外副作用
比如在setter里做异步请求、修改完全不相关的状态,会让代码逻辑变得混乱,后续维护难度陡增。
举个反面例子:
const fullName = computed({
set(newValue) {
// 错误示范:setter里做异步 + 改其他状态
axios.post('/updateName', { name: newValue }).then(() => {
otherState.value = 'xxx' // 改了不相关的状态
})
}
})
这种写法会让“修改fullName”的逻辑变得不可预测。
解决方法:setter只负责“反向更新依赖项”,复杂逻辑拆到单独的methods或Vuex/Pinia的action里。
最后补个小知识点:computed和watch咋选?
虽然这篇讲的是getter/setter,但很多同学会混淆computed和watch,这里顺道对比下:
- computed:专注“同步计算派生值”,依赖变化时自动更新结果,强调“数据 → 数据”的转换;
- watch:专注“监听数据变化后执行副作用”(比如异步请求、DOM操作、复杂逻辑),强调“数据 → 动作”的响应。
举个例子:计算“全名”用computed;监听“路由变化”后加载页面数据,用watch。
Vue3的computed里,getter负责“基于依赖生成值”,setter负责“反向更新依赖”,理解它们的分工和场景,能让你写出更简洁、高效的响应式逻辑~ 下次写表单联动、数据格式化的时候,别再绕弯路啦!
(如果还有疑问,评论区留言,咱们再唠~)
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



