Vue watch 默认不监听对象内部属性变化,需监听顶层响应式对象并配置 deep: true;初始化要完整定义嵌套结构;watchEffect 可自动追踪读取路径,性能更优。

Vue 的 watch 默认只监听对象引用是否变化,不关心内部属性改没改——所以 obj.user.name = '张三' 这类操作不会触发监听。要让它响应,核心是两点:正确启用深度监听,且保证被监听的目标本身是响应式的。
必须监听顶层响应式对象,并开启 deep: true
不能写成 watch(() => obj.a.b, ...) 或 watch('obj.a.b', ...),这种写法即使加 deep: true 也无效,因为中间路径(如 obj.a)可能不是响应式代理对象。
- ✅ 正确做法:监听整个
obj,并显式配置{ deep: true } - 组合式 API 示例:
watch(() => obj, (n, o) => { /* 处理任意层级变化 */ }, { deep: true }) - 选项式 API 示例:
watch: { obj: { handler() { /* ... */ }, deep: true } }
初始化结构要完整,避免中途新增非响应式属性
Vue 的响应式系统无法自动追踪对象上动态添加的属性(比如 obj.newField = {}),除非用 Vue.set(Vue 2)或确保该字段从一开始就在 reactive 或 ref 初始化时存在。
- 推荐初始化时就定义好嵌套结构:
const obj = reactive({ user: { profile: { name: '', age: 0 } } }) - 如果必须后期扩展,用
nextTick后重新赋值整个子对象,或用ref包裹深层对象再嵌套 - 避免直接替换某一层:
obj.user = { profile: { name: '李四' } }—— 这会丢失响应性,应改为逐层修改已存在的响应式属性
Vue 3 更简洁的替代方案:watchEffect
不用手动配 deep,只要在回调里「读取」到需要响应的路径,就会自动建立依赖。
- 示例:
watchEffect(() => { console.log(obj.user?.profile?.name) }) - 可选链
?.不影响依赖收集,前提是obj、user、profile都是响应式对象 - 适合关注特定路径的场景,比
deep: true性能更优,也不会误触无关变更
数组里的嵌套对象也要注意操作方式
deep: true 能监听 arr[0].name = '新名字',但对以下操作无效:
- 直接赋值越界索引:
arr[10] = newItem→ 改用arr.push()或arr.splice(10, 0, newItem) - 清空数组:
arr.length = 0→ 改用arr.splice(0) - 整数组替换:
arr = newArray→ 可以触发,但需确认仍指向原响应式引用(如用ref包裹)
不复杂但容易忽略。关键不在“能不能监听”,而在于“监听谁”和“怎么改”。

















