Vue 3 的 watch 默认不开启深度监听,只响应引用变化;需显式设置 deep: true 才能捕获嵌套属性修改,适用于表单、深层配置等不可预知变更的场景,但会带来性能开销和旧值对比失效问题。

Vue 3 的 watch 默认不开启深度监听,它只响应对象或数组的**引用变化**(比如整个对象被替换),而不会捕获内部属性(如 user.profile.name)的修改。要监听嵌套对象任意子属性的变化,必须显式启用 deep: true 选项。
什么时候需要 deep: true
当你无法预知哪一层会被修改,且必须响应所有嵌套变更时才启用。典型场景包括:
- 表单数据对象(如
form)可能动态增删字段或修改深层值 - 从后端获取的完整 JSON 配置对象,结构较深且变动不可控
- 第三方组件传入的复杂 props,你需对其中任意属性变化做出统一响应
怎么正确开启深度监听
写法有两种,但都必须配合 deep: true:
- 监听整个 reactive 对象:
watch(user, (newVal, oldVal) => { /* ... */ }, { deep: true }) - 监听 getter 函数返回的对象(更常见):
watch(() => user.profile, (newVal, oldVal) => { /* ... */ }, { deep: true })
⚠️ 注意:仅传 user.profile(不带箭头函数)是无效的——那只是取值,不是响应式依赖;必须用 () => user.profile 让 Vue 能追踪访问路径。
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
立即学习“前端免费学习笔记(深入)”;
使用 deep 时的关键细节
旧值对比不可靠:开启 deep 后,oldVal 和 newVal 往往是同一对象引用(浅拷贝),直接用 === 或 == 判断会失效。建议用深比较方式判断差异:
- 简单场景可用
JSON.stringify(oldVal) !== JSON.stringify(newVal) - 生产环境推荐
lodash.isEqual(oldVal, newVal)或自定义 diff 工具
性能开销明显上升:Vue 会递归遍历所有嵌套属性建立依赖。对象层级越深、属性越多,初始化和更新时消耗越大。避免对大型或高频更新的对象无脑开启。
更推荐的替代方案
如果实际只需响应几个特定字段(如 user.profile.city 和 user.status),优先用函数式 getter 精准监听:
- 单字段:
watch(() => user.profile.city, (newVal, oldVal) => {...}) - 多字段组合:
watch([() => user.profile.city, () => user.status], ([c1, s1], [c2, s2]) => {...}) - 带逻辑或可选链的路径,先封装成
computed再监听,更清晰也更安全

















