Vue3里computed的setter和getter到底咋玩?一篇文章给你讲透!
很多刚用Vue3的同学,对computed的setter和getter总是一知半解:默认只有getter?setter咋写?啥时候必须用?今天用问答形式,从基础到实战一次性讲清楚。
computed默认只有getter?先搞懂基础逻辑
在Vue3里写computed,最常见的是函数式写法:
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
这种写法里,computed只负责“读”——也就是getter的工作:根据依赖的响应式数据(比如firstName、lastName)计算结果,这时候要是直接给fullName赋值(比如fullName.value = 'xxx'),控制台会报错,因为默认只有“读权限”,没有“写逻辑”。
那什么时候能“写”?得把computed改成对象形式,主动配置get和set:
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue) {
const [first, last] = newValue.split(' ')
firstName.value = first
lastName.value = last
}
})
这样配置后,执行fullName.value = 'John Doe'时,会触发setter里的逻辑,把firstName和lastName分别修改。
computed默认只开“读权限”(getter),想要“写权限”(setter),必须用对象语法主动配置。
给computed加setter,语法咋写才对?
给computed加setter,核心是把computed的参数从“函数”改成“对象”,对象里必须包含get(负责计算返回值),可选包含set(负责处理赋值逻辑),步骤拆解:
定义依赖的响应式数据
用ref或reactive包装数据(必须是响应式的,否则修改后不会触发界面更新):
import { ref, computed } from 'vue'
const firstName = ref('')
const lastName = ref('')
配置computed的对象格式
对象里的get和set都要接收响应式数据的变化:
const fullName = computed({
get() { // getter:计算并返回结果
return `${firstName.value} ${lastName.value}`
},
set(newValue) { // setter:接收赋值时的新值,处理逻辑
const parts = newValue.split(' ')
if (parts.length >= 2) { // 简单校验,避免拆分出错
firstName.value = parts[0]
lastName.value = parts.slice(1).join(' ') // 处理中间名等情况
}
}
})
页面中使用(双向绑定为例)
读的时候直接用fullName.value,写的时候可以配合v-model:
<template> <input v-model="fullName.value" placeholder="输入全名" /> </template>
当用户在输入框输入内容,会触发setter,修改firstName和lastName,进而让所有依赖这两个变量的地方(比如页面上的其他展示)自动更新。
注意:setter里必须操作响应式数据(如ref/reactive包装的变量),否则修改普通变量不会触发Vue的响应式更新,等于白忙活。
啥场景下非得用setter不可?举几个真实例子
别觉得setter是“花里胡哨”的功能,实际项目里这些场景离了它还真不行:
场景1:表单双向绑定+复杂处理
比如用户资料页,要做一个“全名”输入框(包含姓+名),但后端接口要求姓和名分开存储,这时候用computed的setter,输入框v-model绑fullName,setter负责拆分值到姓和名的响应式变量,getter负责拼接展示,不用setter的话,得自己写input事件处理函数,代码会很零散。
场景2:数据格式双向转换
后端存的是“时间戳(毫秒)”,前端要显示“YYYY - MM - DD”格式,这时候computed的getter把时间戳转成日期字符串,setter把用户输入的日期字符串转回时间戳存到响应式数据里,这样输入输出的格式转换全交给computed,组件逻辑更干净。
场景3:多变量的联动修改
比如购物车的“商品数量+价格”联动,用户改总价时,setter根据单价算出数量(或者反过来),这种多变量依赖的情况,用computed的setter能把逻辑内聚,不用在各个input的事件里重复写计算逻辑。
举个具体例子(场景2:时间格式转换):
import { ref, computed } from 'vue'
import dayjs from 'dayjs'
const timestamp = ref(Date.now()) // 后端返回的时间戳
const dateStr = computed({
get() {
return dayjs(timestamp.value).format('YYYY - MM - DD') // 转成字符串展示
},
set(newStr) {
const newTs = dayjs(newStr).valueOf() // 转成时间戳
timestamp.value = newTs // 存回响应式数据
}
})
页面中用<input v-model="dateStr.value" />,用户输入日期字符串时,setter自动转成时间戳存起来,其他依赖timestamp的组件(比如日历、时间显示)会自动更新——这就是setter的价值:把“展示格式”和“存储格式”的转换逻辑封装在computed里,外层组件不用关心细节。
getter和setter啥时候执行?理清依赖逻辑
很多人搞不清computed的getter和setter的执行时机,其实和Vue3的响应式依赖系统强相关:
getter的执行时机
- 首次访问computed属性时:比如页面第一次渲染
{{ fullName }},或JS里第一次读fullName.value,getter会执行,计算并缓存结果(Vue3的computed有缓存,依赖不变时直接用缓存)。 - 依赖的响应式数据变化时:比如
firstName或lastName变了,下次访问fullName.value时,getter会重新执行,算出新结果。
setter的执行时机
只有主动给computed属性赋值时才会触发,比如fullName.value = '新值',这时候不管依赖的变量有没有变化,都会执行setter里的逻辑。
举个例子理解:
- 初始
firstName='Alice',lastName='Wang'→ get()执行,返回'Alice Wang'。 - 改
firstName为'Bob'→ 此时getter不会立刻执行,只有当再次访问fullName.value时,才会重新计算成'Bob Wang'。 - 执行
fullName.value = 'Charlie Lee'→ 触发setter,把firstName改成'Charlie',lastName改成'Lee'→ 这时候firstName和lastName的变化,会让它们的依赖(比如fullName的getter)标记为“需要重新计算”,但getter不会立刻执行,等下次访问fullName.value时才会重新运行。
getter是“懒执行+缓存”,依赖变了也不立刻跑,等被访问时才更新;setter是“主动触发”,只有赋值时才执行,执行后修改响应式数据,进而让依赖它的getter后续更新。
setter背后的响应式原理,Vue3是咋实现的?
想彻底搞懂setter,得扒开Vue3响应式的底层逻辑,简单说,computed本身是个带缓存的effect,而setter的作用是触发响应式数据的更新,进而让依赖重新计算。
computed的getter是个effect
Vue3里,computed的getter会被包装成一个effect函数(可以理解为“依赖追踪的函数”),这个effect有两个特点:
- 懒执行:默认不立刻执行,只有被访问时才会运行。
- 带缓存:依赖不变时,直接返回缓存结果,避免重复计算。
setter里改响应式数据,触发依赖更新
当你给computed属性赋值(触发setter),setter里的代码会修改其他响应式数据(比如firstName.value = xxx),这时候,Vue的响应式系统会检测到firstName的变化(因为firstName是ref/reactive包装的响应式数据),然后找到所有依赖firstName的effect(比如fullName的getter对应的effect),把这些effect标记为“需要重新运行”。
下次访问computed时,getter重新执行
因为getter是懒执行的,即便firstName变了,getter也不会立刻运行,而是等下次有人访问fullName.value时,才会重新计算最新的结果。
流程梳理:
用户执行fullName.value = '新值' → 触发setter → setter里改firstName.value → Vue检测到firstName的value变化 → 找到依赖firstName的effect(即fullName的getter对应的effect)→ 把这个effect标记为dirty(脏了,需要重新计算)→ 当其他地方访问fullName.value时 → 发现effect是dirty的 → 执行getter重新计算 → 返回新结果。
所以setter的本质是:通过修改响应式数据,间接触发computed getter的重新计算,它不直接处理缓存或依赖,而是借助Vue的响应式系统,把“写computed”的操作转化为“改响应式数据”的操作,从而联动整个依赖链更新。
项目里怎么用setter+getter让代码更优雅?实战案例来了
光讲理论不够,上一个真实项目场景,看看setter+getter怎么让代码从“臃肿”变“丝滑”。
需求:用户信息编辑页,联动显示和修改
页面要显示用户的「姓名(姓+名)」「邮箱」「手机号」,姓名」要做一个输入框,输入全名(张三 四”)时,自动拆分存到firstName和lastName两个字段(后端接口需要分开存),其他字段直接双向绑定。
不用setter的写法(反面教材)
如果不用computed的setter,得引入中间变量和多个watch,代码冗余且易出错:
<template>
<input v-model="fullNameInput" placeholder="输入全名" />
<input v-model="email" placeholder="邮箱" />
<input v-model="phone" placeholder="手机号" />
</template>
<script setup>
import { ref, reactive, watch, computed } from 'vue'
const user = reactive({
firstName: '',
lastName: '',
email: '',
phone: ''
})
// 中间变量:全名输入框的值
const fullNameInput = ref('')
// 初始化:把firstName和lastName拼成fullNameInput
watch([() => user.firstName, () => user.lastName], () => {
fullNameInput.value = `${user.firstName} ${user.lastName}`
}, { immediate: true })
// 监听fullNameInput变化,拆分到firstName和lastName
watch(fullNameInput, (newVal) => {
const [first, last] = newVal.split(' ')
user.firstName = first || ''
user.lastName = last || ''
})
// 邮箱和手机的computed(直接双向绑定也可以,但用computed更灵活)
const email = computed({
get() { return user.email },
set(val) { user.email = val }
})
const phone = computed({
get() { return user.phone },
set(val) { user.phone = val }
})
</script>
问题:中间变量fullNameInput冗余,还要写两个watch同步数据,拆分逻辑没做校验(比如输入空值会报错),代码又多又乱。
用setter+getter的写法(优雅版)
把fullName做成computed,用setter处理拆分逻辑,消除中间变量:
<template>
<input v-model="fullName.value" placeholder="输入全名" />
<input v-model="email.value" placeholder="邮箱" />
<input v-model="phone.value" placeholder="手机号" />
</template>
<script setup>
import { reactive, computed } from 'vue'
const user = reactive({
firstName: '',
lastName: '',
email: '',
phone: ''
})
// 全名的computed,带setter
const fullName = computed({
get() {
return `${user.firstName} ${user.lastName}`.trim() // 去掉多余空格
},
set(newVal) {
const [first, ...rest] = newVal.split(' ') // 处理中间有多个空格的情况
user.firstName = first || ''
user.lastName = rest.join(' ') || '' // 剩下的部分作为lastName
}
})
// 邮箱和手机的computed(封装读写逻辑,也可直接v-model.user.email)
const email = computed({
get() { return user.email },
set(val) { user.email = val }
})
const phone = computed({
get() { return user.phone },
set(val) { user.phone = val }
})
</script>
对比之后的优势
- 消除中间变量:不用再搞
fullNameInput这种冗余的ref,直接用fullName这个computed做双向绑定。 - 逻辑内聚:拆分全名的逻辑全放在
fullName的setter里,初始化、用户输入的逻辑统一,还能加校验(比如处理空值、多空格)。 - 维护性高:以后要改拆分规则(比如支持中间名),只需要改setter里的代码,不用动其他地方。
掌握setter+getter,写出更优雅的Vue代码
Vue3里computed的setter和getter是一对“读写搭档”:
- getter负责“读”:懒执行+缓存,根据依赖计算结果,性能更优。
- setter负责“写”:主动赋值时执行自定义逻辑,修改响应式数据,联动依赖更新。
从语法到原理,再到实战场景,掌握这对搭档能让你在处理「双向转换」「联动修改」「表单复杂逻辑」时,写出更简洁、更内聚的代码,下次碰到“既展示又能改”的计算属性,别忘了解锁setter这个技能~
要是你还有疑问(setter里能调异步函数吗?”“多个computed互相依赖会不会有问题?”),评论区喊我,下次展开讲~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



