Vue3里computed和watch该咋选?一篇搞懂使用场景差异
做Vue3项目时,很多同学刚开始分不清computed和watch啥时候用,明明都是跟数据变化挂钩,咋选才对?今天从核心差异、场景、执行逻辑这些角度拆明白,以后写代码不纠结~
先搞懂:computed和watch核心差异是啥?
你可以把computed理解成“自动计算的属性”,它依赖其他响应式数据,输出一个“派生结果”;而watch是“盯着数据变化后做事的工具”,重点在“数据变了要执行什么动作”。
举个简单对比:
- 用
computed时,你关心“结果是什么”——比如把 firstName + lastName 拼成 fullName,只要 firstName/lastName 变了,fullName 自动更新; - 用
watch时,你关心“变化发生后做什么”——比如用户修改了收货地址,地址变化后要自动调用接口保存到后台,这时候就需要“监听地址变化→执行保存逻辑”。
另外还有个关键区别:computed有缓存机制,只要依赖的响应式数据没变化,多次访问计算属性会直接用缓存结果,不会重复计算;但watch没缓存,只要监听的数据变化,就会触发回调函数执行。
这些场景,优先用computed
如果你的需求是“根据已有数据生成新数据”,选computed准没错,这也是它最擅长的事儿~
处理“数据派生”逻辑
比如用户信息里有firstName和lastName,要显示全名fullName,用computed把两个字段拼起来:
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed(() => `${firstName.value}${lastName.value}`)
只要firstName或lastName变了,fullName会自动更新,模板里直接用{{ fullName }}就行,不用每次手动拼接。
简化模板里的复杂逻辑
模板里尽量少写复杂判断或运算(比如多层嵌套的三元表达式、循环计算),否则代码又丑又难维护,把这些逻辑丢进computed,模板只负责渲染结果。
比如购物车判断商品是否全选:
const allChecked = computed(() => {
return cartList.every(item => item.checked)
})
模板里只用{{ allChecked ? '全选' : '未全选' }},清爽多了~
依赖缓存优化性能
如果一个计算逻辑很耗时(比如遍历大数组做过滤、排序),但依赖的数据不常变化,computed的缓存能帮你省性能。
已完成的任务列表”:
const completedTasks = computed(() => {
return taskList.filter(task => task.done)
})
只要taskList没新增/修改任务,多次访问completedTasks不会重复执行过滤,直接用上次的结果。
这些场景,必须用watch
watch的优势是“数据变化时执行任意逻辑”,尤其是异步操作、多数据联动这些场景,computed搞不定,必须靠watch。
处理异步或副作用操作
computed的回调必须返回一个值,不能写异步代码(比如调接口、定时器),但watch的回调可以随便写异步逻辑。
比如用户修改头像后,要把新头像地址上传服务器:
const avatarUrl = ref('默认头像地址')
watch(avatarUrl, (newUrl) => {
// 调接口上传新地址
uploadAvatar(newUrl).then(() => {
// 上传成功后做其他事
})
})
多数据源联动响应
如果要同时监听多个数据的变化,并且在它们变化时执行同一段逻辑,watch更灵活。
比如表单里“用户名”和“密码”都变了,才允许提交按钮可用:
const username = ref('')
const password = ref('')
const canSubmit = ref(false)
watch([username, password], ([newUser, newPwd]) => {
canSubmit.value = newUser.length > 0 && newPwd.length > 6
})
复杂逻辑的“变化响应”
有些场景逻辑很碎:数据变化后要更新多个状态、调用多个方法、甚至修改其他组件的状态……这种“大动作”适合用watch集中处理。
比如用户切换“暗黑模式”后,要做这些事:
- 切换页面主题样式;
- 把选择记录到 localStorage;
- 通知子组件更新样式;
用watch监听主题变量:const isDarkMode = ref(false) watch(isDarkMode, (newMode) => { document.body.className = newMode ? 'dark' : 'light' localStorage.setItem('theme', newMode ? 'dark' : 'light') emit('theme-changed', newMode) })
监听引用类型的“深层变化”
如果监听的是对象、数组这类引用类型,默认watch只能监听到“引用地址变化”(比如整个对象被重新赋值),如果要监听对象内部属性变化(比如user.info.age变了),得开deep: true选项——这也是computed做不到的。
比如监听用户信息对象的深层变化:
const user = reactive({ info: { age: 18, name: '小明' } })
watch(
() => user.info,
(newInfo) => {
console.log('用户信息变了', newInfo)
},
{ deep: true } // 开启深层监听
)
依赖追踪和执行时机,藏着啥区别?
除了功能场景,computed和watch在“什么时候执行”这件事上也不一样,理解这点能避免很多 bug~
computed:惰性求值 + 缓存复用
computed是“用的时候才计算”(惰性),比如你定义了一个computed属性,但模板里没用到它,那这个计算逻辑永远不会执行,而且只要依赖的数据没变化,下次用的时候直接拿缓存结果,不会重复计算。
举个🌰:
const count = ref(1) const double = computed(() => count.value * 2) // 场景1:模板里没用double → computed回调永远不执行 // 场景2:模板里用了double → 当count从1→2时,double才会重新计算
watch:主动监听 + 即时执行
watch是“数据变了就执行”,不管这个数据有没有被使用,而且每次变化都会触发回调,没有缓存。
比如监听count:
watch(count, (newVal) => {
console.log('count变了,现在是', newVal)
})
// 只要count从1→2→3,每次变化都会触发回调,打印日志
实际项目咋选?举个栗子更清楚
光说理论太虚,结合真实场景看区别~
案例1:购物车“总价计算”→ 用computed
购物车有goodsList(数组,每个商品有price和quantity),要显示“总价”。
用computed的话,依赖goodsList里的每个商品价格和数量,自动计算总和:
const totalPrice = computed(() => {
return goodsList.reduce((sum, item) => {
return sum + item.price * item.quantity
}, 0)
})
只要商品的价格或数量变化,总价自动更新,而且重复访问totalPrice会用缓存,性能友好。
案例2:用户登录后“拉取个人信息”→ 用watch
用户登录状态isLogin从false→true后,要调用接口拉取个人信息、订单列表等。
这时候用watch监听isLogin的变化,执行异步逻辑:
const isLogin = ref(false)
watch(isLogin, (newVal) => {
if (newVal) { // 登录状态变为true时
fetchUserInfo().then((res) => {
userInfo.value = res.data
})
fetchOrderList().then((res) => {
orderList.value = res.data
})
}
})
这种“数据变化后执行异步操作”的场景,computed没法搞,因为它不能返回Promise,也不适合写副作用逻辑。
总结一下怎么选
- 想派生新数据(比如拼接、过滤、计算)→ 用
computed; - 想数据变化后执行动作(比如异步请求、多状态更新、副作用操作)→ 用
watch; - 性能敏感、依赖少变 → 优先
computed(缓存省性能); - 要监听引用类型内部变化、多数据联动 → 必须
watch。
其实记住核心:computed聚焦“结果”,watch聚焦“动作”,下次写代码前,先想清楚你要“得到一个新数据”还是“数据变了要做事”,选择就清晰了~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


