Vue watch 监听 computed 属性?
本地原文仅保留了一个 SegmentFault 链接,下面补充这个问题的完整答案,并保留原始出处。示例同时覆盖 Vue 2 Options API 和 Vue 3 Composition API。
结论
可以监听 computed 属性。computed 负责根据响应式依赖计算并缓存一个派生值,watch 负责在该值发生变化时执行副作用,例如请求接口、记录日志或同步外部状态。
Vue 2 的 Options API 写法
Vue 2 中可以直接在 watch 选项里使用 computed 属性名:
export default {
data() {
return {
firstName: 'Ada',
lastName: 'Lovelace'
}
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
}
},
watch: {
fullName(newValue, oldValue) {
console.log('fullName changed:', oldValue, '=>', newValue)
}
}
}
也可以使用实例方法:
this.$watch('fullName', (newValue, oldValue) => {
// 监听 computed 属性的结果
})
Vue 2.7 仍然保留这套写法,但它的底层响应式实现仍是 getter/setter、Dep 和 Watcher,不是 Vue 3 的 Proxy。
Vue 3 的 Composition API 写法
在 Vue 3 中,computed() 返回的是 computed ref,可以直接作为 watch 的来源:
import { computed, ref, watch } from 'vue'
const firstName = ref('Ada')
const lastName = ref('Lovelace')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
const stop = watch(fullName, (newValue, oldValue) => {
console.log('fullName changed:', oldValue, '=>', newValue)
})
firstName.value = 'Grace'
// 触发 watch:Ada Lovelace => Grace Lovelace
// 不再需要时可以停止监听
// stop()
也可以显式使用 getter:
watch(
() => fullName.value,
(newValue, oldValue) => {
console.log(newValue, oldValue)
}
)
不要传入 computedRef.value
下面的写法通常是错误的:
watch(fullName.value, callback)
如果 fullName.value 是字符串或数字,这只是把当前普通值传给 watch,不会建立响应式依赖。应传入 computed ref 本身,或传入返回其值的 getter:
watch(fullName, callback)
// 或
watch(() => fullName.value, callback)
监听什么时候触发?
watch默认是惰性的,创建时不会立即执行;需要初始化时执行可使用{ immediate: true }。- 只有 computed 的结果发生变化时,普通
watch回调才会触发。computed 的依赖发生变化但计算结果相同,不应把它理解成一定会触发回调。 - 如果 computed 返回对象,监听 computed ref 默认关注对象引用;要观察嵌套属性,需要监听具体 getter,或谨慎使用
{ deep: true }。 - computed getter 应保持纯净,不应在其中发请求、修改其他状态或操作 DOM;这些副作用应放进
watch或watchEffect。
const query = ref('vue')
const result = ref(null)
watch(query, async (newQuery, oldQuery, onCleanup) => {
const controller = new AbortController()
onCleanup(() => controller.abort())
const response = await fetch(`/api/search?q=${encodeURIComponent(newQuery)}`, {
signal: controller.signal
})
result.value = await response.json()
})
Vue 3.5+ 还提供 onWatcherCleanup();异步回调中更通用的写法仍是使用回调的第三个 onCleanup 参数,并在 await 之前完成注册。
watch 和 watchEffect 的选择
如果来源需要明确控制,使用 watch:
watch(fullName, () => {
// 只在 fullName 结果变化时执行
})
如果副作用中读取的响应式依赖就是全部需要追踪的依赖,可以使用 watchEffect:
import { watchEffect } from 'vue'
watchEffect(() => {
console.log(fullName.value)
})
watchEffect 会立即执行,并自动追踪同步执行阶段读取的依赖;异步函数第一次 await 之后读取的属性不会被自动追踪。
小结
computed 可以被 watch。最重要的区别是:
- Vue 2:在
watch选项中写 computed 属性名,或调用vm.$watch()。 - Vue 3:监听
computed()返回的 ref,不要把.value的当前快照传进去。 - computed 用于派生状态和缓存,watch 用于副作用和异步逻辑。