Vue3里computed的getter和setter该怎么用?常见疑问一次讲透
computed里的getter是干啥的?
computed 本质是基于响应式依赖的“智能计算器”,getter 就是定义“怎么算”的逻辑,它有两个核心特点:
自动跟踪依赖
只要 getter 里用到了 ref/reactive 包裹的响应式数据,Vue 就会自动“盯住”这些数据,一旦依赖变化,computed 的值会自动更新。
缓存优化
计算结果会被缓存,只有依赖真的变了才会重新计算,如果模板里多次用到这个 computed 值,依赖不变时不会重复执行 getter,性能更优。
举个购物车例子:
<template>
<div>数量:{{ count }},总价:{{ totalPrice }}</div>
<button @click="count++">增加数量</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const count = ref(2) // 响应式数量
const price = ref(50) // 响应式单价
// getter 定义“总价怎么算”
const totalPrice = computed(() => count.value * price.value)
</script>
当点击按钮让 count 增加时,totalPrice 会自动更新——因为 getter 依赖了 count,Vue 检测到依赖变化后,会重新执行 getter 计算新的总价。
为啥有时候要给computed加setter?
默认情况下,computed 生成的是只读的响应式数据,只能“被动”等依赖变化更新,但有些场景需要主动修改 computed 的值,这时候就得用 setter 定义“怎么改”的逻辑。
典型场景:数据双向转换
比如用户信息的“全名”由 firstName 和 lastName 拼接而成,但从接口拿到的是完整字符串,需要拆分后赋值给 firstName 和 lastName,这时候给 computed 加 setter 就能实现“反向推导”。
代码示例:
<template>
<div>全名:{{ fullName }}</div>
<button @click="setFullName">设置全名</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 用对象形式定义 computed,包含 get 和 set
const fullName = computed({
get() { // 拼接 firstName 和 lastName
return firstName.value + lastName.value
},
set(newFullName) { // 拆分新全名,赋值给 firstName 和 lastName
firstName.value = newFullName[0] // 取第一个字当姓
lastName.value = newFullName.slice(1) // 剩下的当名
}
})
const setFullName = () => {
fullName.value = '李四' // 主动赋值,触发 setter
}
</script>
点击按钮后,fullName 被赋值为 '李四',setter 会把 firstName 改成 '李'、lastName 改成 '四',页面上的全名也会同步更新。
怎么给computed写setter?
语法上,给 computed 传对象(而非单个函数),对象里必须包含 get(),可选 set(val)(val 是赋值时的新值)。
步骤拆解:
- 调用
computed()时,传入{ get() {}, set(val) {} }格式的对象。 get()里写“计算逻辑”,set(val)里写“修改逻辑”(通常要更新依赖的响应式数据)。
再举个“日期格式化”的实用例子:
后端存时间戳(数字),前端展示/输入是 YYYY - MM - DD 格式的字符串,用 computed 的 setter 实现双向转换:
<template>
<input v-model="formattedDate" placeholder="输入YYYY - MM - DD" />
<div>原始时间戳:{{ timestamp }}</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const timestamp = ref(Date.now()) // 存时间戳(响应式数字)
const formattedDate = computed({
get() { // 时间戳 → 字符串格式
const date = new Date(timestamp.value)
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`
},
set(val) { // 字符串 → 时间戳
const [year, month, day] = val.split('-')
const newDate = new Date(+year, month - 1, +day)
timestamp.value = newDate.getTime()
}
})
</script>
用户输入 2024 - 10 - 01 时,setter 会把字符串转成时间戳存到 timestamp;页面展示时,getter 又把时间戳转回格式化字符串——输入输出的格式逻辑被完美封装。
实际项目中哪些场景适合用带setter的computed?
表单数据的双向格式化
后端存“分”(100 分 = 1 元),前端展示“元”,用 computed 的 setter 做单位转换,减少模板里的逻辑:
<template>
<input v-model="displayAmount" placeholder="输入金额(元)" />
<div>实际存储(分):{{ storageAmount }}</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const storageAmount = ref(0) // 存分(响应式数字)
const displayAmount = computed({
get() { return storageAmount.value / 100 }, // 分 → 元
set(val) { storageAmount.value = val * 100 } // 元 → 分
})
</script>
复杂状态的联动控制
比如表格“全选”功能:全选状态依赖所有子选项,点击全选框时要修改所有子选项,用 computed 把“子→全”和“全→子”的逻辑内聚:
<template>
<input type="checkbox" v-model="isAllChecked" /> 全选
<div v-for="(item, idx) in list" :key="idx">
<input type="checkbox" v-model="item.checked" /> {{ item.name }}
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const list = ref([
{ name: '选项1', checked: false },
{ name: '选项2', checked: false },
{ name: '选项3', checked: false }
])
const isAllChecked = computed({
get() { // 子选项全选 → 全选框勾选
return list.value.every(item => item.checked)
},
set(val) { // 全选框勾选 → 所有子选项勾选
list.value.forEach(item => { item.checked = val })
}
})
</script>
前后端数据格式适配
后端返回蛇形命名(如 user_name),前端用驼峰(如 userName),用 computed 的 setter 自动转换格式,减少手动处理:
<template>
<div>用户名:{{ userName }}</div>
<button @click="fetchData">模拟请求</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const rawData = ref({ user_name: 'zhangsan' }) // 后端结构
const userName = computed({
get() { return rawData.value.user_name }, // 蛇形 → 驼峰(读取)
set(val) { rawData.value.user_name = val } // 驼峰 → 蛇形(存储)
})
const fetchData = () => {
rawData.value = { user_name: 'lisi' } // 模拟接口更新
}
</script>
computed的getter/setter和watch有啥区别?
很多人会混淆这俩,核心区别在“声明式依赖” vs “响应式副作用”,以及使用场景:
依赖处理方式
- computed(getter):自动跟踪依赖,只要 getter 里用了响应式数据,Vue 会自动“盯住”这些数据,依赖变化时重新计算,而且有缓存,依赖不变时直接用缓存值。
- watch:手动指定依赖,要明确写监听的数据源(如
watch(count, () => {})),每次依赖变化都会执行回调,没有缓存。
使用场景
- computed:适合“计算一个值”(由其他数据推导而来),带 setter 后还能处理“反向推导”(如格式转换、状态联动)。
- watch:适合“响应变化做副作用”(如发请求、操作 DOM、修改多个状态)。
代码简洁度对比(全选功能)
用 computed(逻辑内聚):
const isAllChecked = computed({
get() { return list.every(...) },
set(val) { list.forEach(...) }
})
用 watch(逻辑分散):
const isAllChecked = ref(false)
// 子选项变化 → 更新全选
watch(() => list.every(...), (newVal) => { isAllChecked.value = newVal })
// 全选变化 → 更新子选项
watch(isAllChecked, (newVal) => { list.forEach(...) })
用computed的getter/setter容易踩哪些坑?
setter里修改依赖,导致循环更新
setter 里修改了 getter 依赖的响应式数据,可能触发“修改→重新计算→再修改”的循环。
反例:
const count = ref(1)
const double = computed({
get() { return count.value * 2 },
set(val) { count.value = val / 2 } // 修改count,触发getter重新计算
})
double.value = 4 // 触发setter → count变2 → getter计算double为4 → 又触发setter?
解决:复杂逻辑拆分到多个响应式数据,或用 watch 处理“副作用”,避免 setter 里直接修改依赖。
getter里有“不纯”操作,导致缓存失效
getter 必须是纯函数(相同输入返回相同输出,无副作用),如果包含“取当前时间”“随机数”“修改其他变量”等操作,会导致缓存失效或数据不一致。
反例(取当前时间):
const now = computed(() => new Date().getTime()) // 依赖不变(没有响应式数据),now.value永远是第一次计算的时间,不会实时更新
解决:实时数据用 ref + watchEffect,或定时更新响应式数据。
响应式丢失(返回非响应式数据)
getter 返回普通对象/数组(没被 reactive/ref 包装),后续修改不会触发更新。
反例:
const rawList = ref([1,2,3]) const filteredList = computed(() => rawList.value.filter(num => num > 1)) // 返回普通数组,不是响应式的,后续修改无法触发更新
解决:返回 reactive 或 ref 包装的数据,或把修改逻辑放到 setter 里。
忽略setter的参数类型,导致逻辑错误
setter 的参数是“赋值时的新值”,要确保类型匹配,比如全名例子中,若用户输入非字符串,split 会报错。
解决:setter 里加类型检查,或在模板限制输入类型(如 input type="text")。
掌握computed的getter/setter,让响应式逻辑更丝滑
Vue3 的 computed 通过 getter 和 setter,把“数据推导”和“反向修改”的逻辑优雅封装:
- 只读场景:用 getter 足够,自动跟踪依赖+缓存,性能拉满;
- 双向转换场景(如格式、状态联动):用带 setter 的 computed,让逻辑内聚,代码更易维护;
- 避坑关键:getter 写纯函数、setter 防循环更新、返回值确保响应式;
- 和 watch 配合:计算逻辑归 computed,副作用逻辑归 watch。
多写 demo 测试表单格式化、状态联动等场景,遇到问题先检查依赖跟踪和 setter 逻辑——掌握这些,响应式编程会越来越顺手,代码也会更简洁优雅~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网

