先记住这个答案
在开发模式下,onTrack 用来观察响应式依赖被收集,onTrigger 用来观察这些依赖发生变化并触发效果的事件。调试事件包含目标、操作类型和属性键等信息,可用于检查是否漏读、读多了或写错了源。它们观察的是响应式过程,不是稳定的业务事件接口;批量调度、源值比较等环节可能让触发次数和最终回调次数不同。
- onTrack 看实际收集的依赖
- onTrigger 看导致触发的操作
- 调试钩子仅用于开发环境诊断
用事件键核对预期监听范围
例如只想监听用户姓名,却因为 getter 展开整个对象而读入多个字段,就可能产生额外触发。先检查 onTrack 记录的 key 与预期字段是否一致,再去调试业务回调能缩小问题范围。
watch 的源函数负责收集依赖,回调里额外读取的字段不会自动变成显式源的一部分。watchEffect 则收集同步执行时实际读取的内容,所以同一段业务逻辑换接口后,调试事件范围也可能改变。
记录最小信息并设置断点
下面将事件缩减成阶段、操作和键名,避免把代理对象整体输出后在控制台展开时看到另一个时间点的值。开发时也可以在钩子内设置断点,沿调用栈定位哪个赋值触发了观察。
不要在这些钩子里修复状态、发请求或驱动核心功能。一方面生产构建不会提供同样的调试行为,另一方面调试代码本身读写响应式状态也会增加干扰,使依赖关系更难解释。
import { reactive, watch } from 'vue'
const state = reactive({ count: 0, note: '' })
const events: string[] = []
const seen: number[] = []
const stop = watch(() => state.count, (value) => {
seen.push(value)
}, {
onTrack(event) {
events.push('track:' + event.type + ':' + String(event.key))
},
onTrigger(event) {
events.push('trigger:' + event.type + ':' + String(event.key))
}
})在开发运行时改变 note 不应触发这个仅读取 count 的源,改变 count 则能看到相关触发事件。具体日志总条数受重新收集与调度影响,诊断应围绕目标属性和业务结果,而非硬编码所有内部事件数量。
触发了却没执行回调如何分析
同步把数值改为一再改回零,依赖层可以发生触发,但默认批量刷新时 getter 的最终结果可能仍等于旧值,于是 watch 回调无需执行。需要同时记录源最终值和等待刷新的时点来解释。
如果开发环境有日志而生产环境没有,先确认是否误把调试钩子当成业务回调。若要长期观察请求次数或耗时,应在实际业务路径记录指标,使用明确的数据契约,而不是依赖开发调试事件。
容易答错的地方
- 把 onTrigger 当成回调执行计数
- 依赖触发位于调度和最终值判断之前,多个写入可能合并成一次回调,也可能最后没有净变化。统计业务执行时应直接在回调入口记录,不要由调试事件数量反推请求次数。
- 把完整代理对象作为历史快照
- 控制台展示对象时可能读取当前状态,后来展开看到的内容未必是触发当时的值。对定位必要的简单字段当场取值保存,复杂数据则明确制作快照并控制体积和敏感信息。
面试官还会怎么问?
为什么生产环境没有 onTrack 日志?
这些响应式调试回调面向开发模式,不能承担生产功能。若线上需要诊断,应围绕可观察业务行为设计轻量日志,并记录资源编号和操作来源等能关联问题的信息。
getter 没有 onTrack 记录说明什么?
可能 getter 只读取普通变量,或者读取发生在未被跟踪的阶段,也要确认运行的是开发构建。先用一个简单 ref 的 value 读取建立对照,再逐步恢复真实 getter,避免直接归因框架失效。
如何从 onTrigger 追到真正的写入位置?
在调试钩子中设置断点并检查调用栈,结合事件的目标、键和操作类型定位写入。若变化来自异步任务,还应补充业务任务标识,否则仅凭属性名难以区分多个请求的回填。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。