《Vue3 从入门到大神37篇》Vue3 源码详解(七):watch 与 watchEffect 源码对比——副作用是如何被追踪的? 本篇关键词watch、watchEffect、job、scheduler、flush、pre / post / sync、cleanup难度等级⭐⭐⭐⭐⭐工程价值watch 是 Vue3 中最复杂的响应式 API。读懂它你才能真正理解“副作用调度”“异步更新”“竞态处理”这些高阶工程问题。一、开篇为什么 watch 比 computed 复杂很多同学觉得“watch 不就是监听数据变化吗”但真实情况是为什么watchEffect会自动收集依赖为什么watch需要明确指定 source为什么flush: post能拿到更新后的 DOM为什么 watch 能拿到新旧值而 watchEffect 不行为什么 watch 能防止“竞态问题”答案不在 API 层面而在调度系统Scheduler中。二、一句话区分 watch 和 watchEffectAPI本质watchEffect立即执行的副作用 自动依赖收集​watch显式数据源 惰性执行 新旧值对比 调度控制​watch 是 watchEffect 的“增强版”。三、watchEffect 源码解析自动依赖收集的真相1️⃣ 使用方式watchEffect(() { console.log(state.count) })特点✅ 立即执行✅ 自动收集依赖❌ 拿不到旧值❌ 无法精确控制触发时机2️⃣ watchEffect 的核心实现简化版源码位置packages/runtime-core/src/apiWatch.tsfunction watchEffect( effectFn, options {} ) { return doWatch(effectFn, null, options) }所有魔法都在doWatch里。3️⃣ watchEffect 的 effect 创建const effect new ReactiveEffect(effectFn) effect.scheduler () { if (flush sync) { job() } else { queueJob(job) } }关键点watchEffect内部也是一个ReactiveEffect通过scheduler接入 Vue 的异步调度系统默认是异步执行flush: pre4️⃣ 为什么 watchEffect 能自动收集依赖effect.run()执行effectFn时读取响应式数据触发track建立依赖关系和普通 effect 没有任何区别。四、watch 源码解析精确控制的艺术1️⃣ watch 的使用方式watch( () state.count, (newVal, oldVal) { console.log(newVal, oldVal) }, { flush: post, immediate: true } )特点✅ 明确指定 source✅ 拿到新旧值✅ 支持immediate✅ 支持deep✅ 支持flush控制2️⃣ watch 的核心实现简化版function watch(source, cb, options) { return doWatch(source, cb, options) }没错watch 和 watchEffect 共用同一个doWatch。区别在于有没有 callback。五、doWatchwatch 系统的真正大脑1️⃣ source 的三种形态Vue3 支持三种 watch source// 1️⃣ getter watch(() state.count, cb) // 2️⃣ ref watch(countRef, cb) // 3️⃣ reactive watch(state, cb, { deep: true })源码中统一处理let getter: () any if (isRef(source)) { getter () source.value } else if (isReactive(source)) { getter () source deep true } else if (isFunction(source)) { getter source }2️⃣ jobwatch 回调的执行单元const job () { if (cb) { const newValue effect.run() if (deep || hasChanged(newValue, oldValue)) { cb(newValue, oldValue) oldValue newValue } } else { effect.run() } }新旧值对比发生在 job 中而不是 trigger 中。3️⃣ 为什么 watch 能拿到旧值核心秘密let oldValue initialValue oldValue newValueeffect.run()得到新值与缓存的旧值对比符合条件才执行回调✅这就是 watch 能精准控制更新的原因。六、flush 机制pre / post / sync 的本质这是 watch 最难理解但工程价值最高的部分。1️⃣ flush: pre默认flush: pre执行时机组件更新前源码逻辑queuePreFlushCb(job)✅适合大多数业务逻辑2️⃣ flush: postflush: post执行时机组件更新后 → DOM 已更新源码逻辑queuePostRenderEffect(job, instance)✅适合操作 DOM、获取尺寸、Scroll 位置watch(source, cb, { flush: post }) onMounted(() { // 等价于 watch post })3️⃣ flush: syncflush: sync执行时机数据变化 → 立即执行源码逻辑job()⚠️慎用可能导致多次同步更新性能差。七、deep watch性能与精度的权衡1️⃣ 为什么 reactive 默认 deepwatch(state, cb)因为getter () source track(state, GET, ITERATE_KEY)一次性追踪整个对象。2️⃣ deep 的实现原理function traverse(value, seen new Set()) { if (!isObject(value) || seen.has(value)) return seen.add(value) for (const key in value) { traverse(value[key], seen) } return value }递归访问每一个属性强制触发 track。⚠️性能代价极高慎用 deep。八、cleanup解决竞态问题的关键1️⃣ 竞态问题示例watch(id, async (newId) { const res await fetchData(newId) data.value res })如果id快速变化请求 A 发出请求 B 发出B 先返回A 后返回 →数据错乱2️⃣ watch 的 cleanup 机制watch(id, async (newId, oldId, onCleanup) { let canceled false onCleanup(() { canceled true }) const res await fetchData(newId) if (!canceled) { data.value res } })源码本质const cleanup effect.onStop || (() {})每次重新执行前先执行上一次的 cleanup。✅这是前端“竞态控制”的标准解法。九、watchEffect vs watch源码级对比表维度watchEffectwatch依赖收集自动显式立即执行✅可选新旧值❌✅深度监听❌✅调度控制基础精细竞态处理❌✅适用场景副作用数据变化响应十、调试实战观察 watch 的调度过程在 VSCode 中打这些断点packages/runtime-core/src/apiWatch.ts ├── doWatch() ├── job() ├── queueJob() └── cleanup()测试代码watchEffect(() { console.log(effect, state.count) }) watch(() state.count, (n, o) { console.log(watch, n, o) }) state.count观察queueJob何时入队job何时执行DOM 更新前后顺序你会彻底理解 Vue3 的异步更新机制。十一、工程最佳实践✅ 优先使用 watchEffectwatchEffect(() { document.title state.title })✅ DOM 相关用 flush: postwatch(element, () { el.getBoundingClientRect() }, { flush: post })✅ 异步请求必用 cleanupwatch(id, (newId, _, onCleanup) { const abort new AbortController() onCleanup(() abort.abort()) fetch(url, { signal: abort.signal }) })❌ 避免 deep watch// ❌ 性能差 watch(state, cb, { deep: true }) // ✅ 精确监听 watch(() state.user.name, cb)十二、本篇总结✅ watch / watchEffect 共用doWatch✅ 调度系统queueJob是异步更新的核心✅ flush 控制执行时机pre / post / sync✅ cleanup 是解决竞态问题的关键✅ deep watch 有显著性能代价一句话升华watch 的本质不是“监听数据”而是“在正确的时机以正确的方式执行正确的副作用”。