vue中watch默认浅层监听,仅响应引用变化;需设deep: true才监听内部属性变动,因其性能开销大且多数场景只需关注状态替换。

Vue 中 watch 默认是浅层监听(shallow),也就是说它只响应被监听目标的**引用变化**,而不是其内部属性的变动。比如你 watch 一个 reactive 对象,当这个对象被整体替换成另一个新对象时,watch 会触发;但如果只是修改它某个属性(如 obj.count++),默认情况下不会触发——除非你显式开启深度监听。
为什么默认不深度监听
性能和语义清晰是两个主要原因:
- 深度监听需要递归遍历整个对象树,建立响应式依赖,开销明显更大,尤其对大型嵌套对象或频繁更新的场景不友好
- 多数业务场景中,watch 的意图是“关注某个状态是否被替换”,比如切换用户、加载新数据包、接收新的 API 响应体等,这些本身就是引用级变更
- 若默认深度监听,每个 watch 都可能无意中拖慢渲染或引发冗余更新,违背响应式系统的“按需追踪”原则
如何开启深度监听
在选项式 API 或组合式 API 中,都通过 deep: true 配置项启用:
- 选项式:
watch: { userInfo: { handler() {}, deep: true } } - 组合式:
watch(() => state.userInfo, handler, { deep: true })
注意:deep: true 对 ref 包裹的普通对象也生效,但对基本类型(string/number/boolean)无意义——它们没有内部属性可“深”监听。
深度监听的实际效果
开启后,watch 会捕获以下所有变更:
obj.name = 'Alice'obj.profile.avatar = '/new.jpg'-
obj.list.push(100)(前提是 list 是 reactive 数组) - 甚至
delete obj.age(如果该属性原本是响应式的)
本质是 Vue 内部会对 watched 值执行一次 traverse(递归读取),从而让所有嵌套的响应式属性都建立依赖关系。
替代方案:更精准地监听特定路径
比起盲目开 deep: true,有时更高效的做法是:
- 用计算属性提取关注字段:
const userName = computed(() => user.name),再 watch 这个计算属性 - 监听 getter 函数:
watch(() => user.profile?.email, ...),只响应 email 变化,不关心 profile 其他字段 - 对数组用
watch(..., { immediate: true, deep: false })+ 手动比对前后值(适合简单结构)
这样既避免了不必要的依赖收集,又保持了逻辑清晰和可维护性。










