先记住这个答案
watch 的 deep 选项用于深入观察来源对象的嵌套变化,避免只看 getter 返回对象的身份是否替换。它需要访问相应深度的属性以建立依赖,数据结构越大、变化越频繁,额外工作越值得关注。Vue 3.5 起也支持用数字限制最大遍历深度。deep 不会克隆对象,因此由同一对象内部修改触发时,新旧参数可能是同一个引用。应按副作用所需字段选择最小合理范围。
- 深层观察需要相应属性遍历与依赖建立
- 数字 deep 有 Vue 3.5 版本边界,不是时间或次数限制
- deep 与历史快照、字段差异列表是不同能力
深监听观察内容,默认 getter 更关注结果变化
如果 getter 返回 form 对象,而用户只改 form.address.city,返回的仍可能是同一个对象。启用 deep 后,嵌套属性变化也能触发观察。直接监听 reactive 对象本来就有默认深层行为,所以分析配置时要先确认 source 形式,不能看到没有 deep 字样就认为必然是浅监听。
深监听不是 JSON 序列化比较,也不是每次把完整对象复制两份。它通过响应式读取建立对嵌套变化的观察,因此回调拿到的 oldValue 可能已经反映当前对象内容。若要知道具体改了什么,必须设计额外快照、字段监听或动作记录,而不是假定 deep 已经提供差异报告。
大型编辑状态不宜无差别触发相同工作
例如编辑器状态包含正文、选择区域、主题配置和附件队列,而自动保存只需要正文与附件。直接深监听整个对象,光标或主题变化也可能触发保存相关流程,回调里再完整序列化一次,会把观察范围和业务成本一起扩大。先分清哪些字段影响保存,再选择具体来源。
如果业务确实需要保存任意嵌套字段,也可以对保存过程进行合理调度与版本管理,但防抖并不会自动消除所有依赖遍历成本。还要处理请求竞争、未完成保存和页面离开时的语义,不能把深监听加一个延时就当成完整的自动保存设计。
用深度限制时明确哪个边界以内需要观察
Vue 3.5 的数字 deep 表示最大遍历深度,可以帮助限制层级范围,但它不会把任意深处修改汇总成根部变化。设得过浅可能漏掉真正需要观察的字段,设得过深又可能接近全面遍历,应根据数据结构设计具体验证用例。
浅层状态、外部不可变对象和第三方实例还需要考虑转换边界;不能期待 deep 对所有非响应式内部写入产生通知。若外部数据按整体快照替换,观察版本或根引用可能更清楚。优化的前提是更新协议可靠,不能靠减少监听范围悄悄忽略必要变化。
容易答错的地方
- deep 会把 oldValue 自动变成修改前的深拷贝
- 深监听不承担历史保存。对象内部原地变化时,新旧参数可能指向同一对象,想比较前后内容需要明确保存策略。
- 给所有 watch 加 deep 最稳妥
- 观察范围扩大可能带来无关副作用和成本,也会掩盖不清楚的输入关系。应该覆盖真实需求,而不是用一个选项替代状态与副作用设计。
面试官还会怎么问?
deep: 1 是只触发一次吗?
不是,它在支持数字深度的 Vue 版本中限制遍历深度。只运行一次回调是 once 的职责,两个选项表达不同含义。
防抖能完全解决深监听性能问题吗?
不能保证,防抖主要减少后续回调中的某些工作,来源追踪、状态更新和已有调度仍有成本。应分别测量遍历、回调与网络等环节。
只关心几个深层字段,怎样写更清楚?
可以通过 getter 或来源数组直接读取需要的字段,避免覆盖整棵树。如果字段集合动态变化,再明确建模规则与生命周期,而不是默认监听一切。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。