直接传 reactive 对象给 watch 无法监听内部属性变化,必须用箭头函数包裹访问路径(如 () => user.name)才能建立响应式依赖,否则仅监听引用替换。

直接传 reactive 对象本身给 watch 是监听不到内部属性变化的——比如 user.name = '李四' 不会触发回调。必须让 Vue 明确“你关心的是哪条访问路径”,才能建立响应式依赖。
用箭头函数包裹属性访问路径
这是最推荐、最可靠的方式。Vue 会在执行该函数时自动追踪所有被读取的响应式属性,一旦其中任意一个变化,回调就触发。
-
✅ 正确写法(监听 name):
watch(() => user.name, (newVal, oldVal) => { console.log('名字变了', oldVal, '→', newVal) }) -
✅ 嵌套属性也一样(监听 address.city):
watch(() => user.address.city, (newVal, oldVal) => { console.log('城市更新了') }) -
❌ 错误写法(传整个对象或子对象):
watch(user, ...)或watch(user.address, ...)—— 这只监听引用是否被替换,不响应内部修改
为什么不能直接传 user.address?
user.address 在传入 watch 的瞬间就被求值,返回的是一个普通对象(即使它本身也是 reactive 的)。Vue 失去了对原始代理对象的引用,无法在后续属性赋值时触发依赖更新。本质上是响应式链断开了。
- 解构也会导致同样问题:
const { name } = user→name是普通字符串,不再响应 - 如需解构,应使用
toRefs(user),它返回的每个属性仍是 ref,保持响应式连接
补充:ref 包裹的对象也要同样处理
即使你用 ref({ profile: { avatar: '' } }),监听深层属性仍需函数式写法:
-
✅ 推荐:
watch(() => userInfo.value.profile.avatar, ...) -
❌ 不推荐:
watch(userInfo, ..., { deep: true })——deep对 ref 的 value 层容易误判,且oldValue不可靠
性能与语义兼顾的小技巧
函数式监听不仅准确,还自带性能优势:只有你明确访问的那条路径发生变化时才触发,避免无谓遍历整棵树。
- 多个相关属性可合并监听:
watch(() => [user.name, user.age], ([n1, n2], [o1, o2]) => {...}) - 若需立即执行一次,加
{ immediate: true }选项 - 回调中能拿到准确的
oldValue,适合做差异比对或撤销逻辑
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










