Vue3 Composition API里computed的get和set咋用?
很多刚接触Vue3 Composition API的同学,对计算属性的get和set容易犯迷糊:明明Options API里的computed还挺熟,换成Composition API咋写读写逻辑?啥场景需要给computed加set?写的时候又要注意啥?今天咱们一个个掰扯清楚。
Composition API的computed和Options API的computed有啥不同?
先回忆下Options API里的computed:在computed选项里定义对象,每个属性可以是仅get(函数形式),或者包含get和set的对象,比如处理全名拼接:
export default {
data() { return { firstName: '', lastName: '' } },
computed: {
fullName: {
get() { return this.firstName + this.lastName },
set(val) {
this.firstName = val.slice(0, 1)
this.lastName = val.slice(1)
}
}
}
}
Composition API里,computed变成导入的函数,用法更“函数式”,核心区别是:
- 写法上:得先
import { computed } from 'vue',再在setup或<script setup>里调用computed()创建计算属性。 - 作用域上:Composition API的
computed是局部变量(在setup里定义),和其他响应式数据、函数平级,更适合逻辑拆分(比如把计算属性逻辑抽到单独的composable里)。
举个Composition API的例子:
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('')
const lastName = ref('')
// 用computed函数创建,传入{ get, set }对象
const fullName = computed({
get() { return firstName.value + lastName.value },
set(val) {
firstName.value = val.slice(0, 1)
lastName.value = val.slice(1)
}
})
</script>
简单说,功能逻辑没大变,但写法更灵活,和Composition API的“组合式”理念更契合。
啥时候需要给computed加set?
计算属性默认只有getter(只读),Vue会自动缓存计算结果,但如果需要让计算属性能被赋值(比如表单双向绑定、反向推导依赖值),就得加setter。
举几个典型场景:
- 表单联动:比如用户填“全名”输入框,要同步拆分到“姓”和“名”两个输入框;反之,改“姓”或“名”,全名也要自动更新,这时候全名作为计算属性,需要支持“写”操作(
set)。 - 反向推导依赖:比如购物车中“总价=单价×数量”,用户直接改总价时,要反向算出数量(假设单价不变),这时候总价作为计算属性,
set里要处理数量的更新。 - 复杂状态整合:多个子状态(如年、月、日)组合成一个“日期字符串”,改字符串时要拆分回子状态,这也需要
setter。
一句话:当你需要让“计算出来的值”能被主动修改,并反向影响它的依赖源时,就得给computed加set。
怎么写computed的get和set逻辑?
步骤很清晰,咱们结合例子一步步来(就用“全名拆分”的场景):
步骤1:准备响应式依赖
先用ref或reactive定义要依赖的响应式数据,比如姓和名:
import { ref } from 'vue'
const firstName = ref('') // 姓
const lastName = ref('') // 名
步骤2:调用computed,传入{ get, set }
用computed()创建计算属性,参数是一个对象,包含get和set两个函数:
import { computed } from 'vue'
const fullName = computed({
// 1. get函数:返回计算后的值(依赖firstName和lastName)
get() {
return firstName.value + lastName.value
},
// 2. set函数:接收“新值”,处理后更新依赖(firstName和lastName)
set(newFullName) {
// 这里写拆分逻辑(示例是简单拆分,实际要考虑格式验证)
firstName.value = newFullName.slice(0, 1) // 假设姓1个字
lastName.value = newFullName.slice(1) // 剩下的是名
}
})
步骤3:在模板中使用(双向绑定)
计算属性和普通响应式数据一样,能直接用v-model双向绑定:
<template> <input v-model="firstName" placeholder="姓" /> <input v-model="lastName" placeholder="名" /> <!-- 双向绑定fullName,改它会触发set --> <input v-model="fullName" placeholder="全名" /> </template>
关键点:
get里必须访问响应式数据(ref/reactive的属性),Vue才会自动追踪依赖,依赖变化时重新计算。set里必须主动修改响应式依赖(比如firstName.value),这样才能触发界面更新。
get和set里的响应式依赖咋处理?
这部分容易踩坑,得仔细看:
getter的依赖追踪
Vue的computed是“惰性求值+缓存”的:只有当getter被访问时,才会计算值;而且如果依赖(比如firstName、lastName)没变化,下次访问会直接读缓存。
getter里只要访问了响应式数据的.value(ref)或属性(reactive),Vue就会自动把这些数据当作“依赖”,一旦依赖变化,下一次getter被调用时,就会重新计算。
比如前面的例子,get里用了firstName.value和lastName.value,所以当这两个值变化时,fullName的getter会自动重新计算。
setter的依赖更新
setter的作用是接收新值,反向修改依赖,注意:
setter里不能直接修改computed本身(比如fullName.value = newVal),否则会触发自己的setter,陷入无限循环!- 必须修改其他响应式数据(比如
firstName、lastName),这样这些数据的变化会触发它们的依赖更新(包括fullName的getter)。
举个错误示范(千万别这么写):
const fullName = computed({
get() { ... },
set(val) {
fullName.value = val // 死循环!set里改自己,会再次触发set
}
})
正确的做法是修改依赖源,比如前面的firstName和lastName。
用computed带set时要避开哪些坑?
踩过这些坑,才算真正掌握:
循环依赖(无限递归)
只要setter里修改computed自身,就会无限触发set。set里只改依赖源,别碰computed自己。
拆分/处理逻辑不严谨
全名拆分”场景,如果用户输入“欧阳”(姓两个字),按之前的slice(0,1)逻辑,姓会变成“欧”,名变成“阳”,明显不符合预期。
解决办法:set里的逻辑要考虑边界情况,必要时加格式验证,比如拆分全名时,先判断是否有空格,按空格分割姓和名:
set(newFullName) {
const [first, last] = newFullName.split(' ') // 假设用户按“姓 名”格式输入
firstName.value = first || ''
lastName.value = last || ''
}
响应式丢失(get里用了非响应式数据)
如果get里访问的是普通变量(不是ref/reactive),Vue没法追踪依赖,computed就不会更新。
错误例子:
let firstName = '张' // 普通变量,不是响应式
const lastName = ref('三')
const fullName = computed({
get() { return firstName + lastName.value } // firstName非响应式,改它不会触发更新
})
解决:把普通变量用ref或reactive包装,确保get里访问的是响应式数据。
性能问题(set里逻辑太繁重)
如果set里要修改多个响应式数据,且这些数据还有很多依赖,可能导致界面频繁更新。
优化思路:
- 尽量简化
set里的逻辑,必要时用nextTick分批更新; - 对不需要实时同步的场景,考虑用
watch代替computed(但computed更适合声明式逻辑)。
实际项目里computed get+set的典型场景有啥?
光说不练假把式,看几个真实项目里的例子:
场景1:表单双向绑定的复杂处理(日期拆分)
需求:用户可以通过“年-月-日”的输入框改日期,也能通过单独的年、月、日输入框改,两边实时同步。
代码实现:
<script setup>
import { ref, computed } from 'vue'
const year = ref(2000)
const month = ref(1)
const day = ref(1)
const dateStr = computed({
get() {
return `${year.value}-${month.value}-${day.value}`
},
set(val) {
// 拆分字符串为年、月、日
const [y, m, d] = val.split('-')
year.value = Number(y)
month.value = Number(m)
day.value = Number(d)
}
})
</script>
<template>
<input v-model="dateStr" placeholder="yyyy-mm-dd" />
<input v-model.number="year" type="number" placeholder="年" />
<input v-model.number="month" type="number" placeholder="月" />
<input v-model.number="day" type="number" placeholder="日" />
</template>
这里dateStr作为计算属性,既能整合年、月、日,又能反向拆分,完美支持双向绑定。
场景2:购物车总价反向推导数量
需求:商品“总价=单价×数量”,用户改总价时,自动计算数量(假设单价不变)。
代码实现:
<script setup>
import { ref, computed } from 'vue'
const price = ref(10) // 单价(假设固定)
const quantity = ref(2) // 数量
const total = computed({
get() {
return price.value * quantity.value
},
set(newTotal) {
quantity.value = newTotal / price.value // 反向算数量
}
})
</script>
<template>
<p>单价:{{ price }}</p>
<input v-model.number="quantity" type="number" placeholder="数量" />
<input v-model.number="total" type="number" placeholder="总价" />
</template>
用户改total时,quantity会自动更新;改quantity时,total也会自动更新,逻辑非常简洁。
场景3:权限状态的整合与反向设置
需求:系统里“是否管理员”由多个子权限(如canManageUser、canManageOrder)决定;设置isAdmin时,要批量设置子权限。
代码实现(简化版):
<script setup>
import { reactive, computed } from 'vue'
const permissions = reactive({
canManageUser: false,
canManageOrder: false,
canManageGoods: false
})
const isAdmin = computed({
get() {
// 假设管理员需要同时有三个权限
return (
permissions.canManageUser &&
permissions.canManageOrder &&
permissions.canManageGoods
)
},
set(isAdmin) {
// 设置isAdmin时,批量更新子权限
permissions.canManageUser = isAdmin
permissions.canManageOrder = isAdmin
permissions.canManageGoods = isAdmin
}
})
</script>
<template>
<label>
<input type="checkbox" v-model="isAdmin" /> 是管理员
</label>
<!-- 子权限也能单独控制 -->
<label>
<input type="checkbox" v-model="permissions.canManageUser" /> 管理用户
</label>
<!-- 其他子权限同理 -->
</template>
这里isAdmin作为计算属性,整合了多个子权限;设置isAdmin时,又能反向批量设置子权限,逻辑很清晰。
总结一下
Vue3 Composition API的computed,在get和set的用法上,核心逻辑和Options API一脉相承,但写法更函数式、更灵活,记住这几点:
- 只有需要“写回依赖源”时,才给
computed加set; get里访问响应式数据,Vue自动追踪依赖;set里修改其他响应式数据,别碰computed自己;- 实际场景中,表单联动、反向推导、状态整合都是典型用武之地。
多写几个例子,踩踩坑(比如循环依赖、拆分逻辑不严谨),自然就掌握了~
如果还有疑问,computed和watch咋选?”“reactive对象在computed里咋处理?”,评论区喊我,下次再展开唠~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



