第 30 题:Vue3 中 watchEffect 的原理(依赖自动追踪 + 清理机制 + ReactiveEffect 全流程)
依旧采用:
核心回答 → 深度原理 → 源码流程图 → 例子 → 面试追问 → 金牌总结
🎯 一、核心回答(面试必答版)
watchEffect 的核心原理是:
自动依赖收集 + 自动触发 + 自动清理
它会立即执行一次回调,并在访问响应式数据时收集依赖;当依赖变化时自动重新执行。
与 watch 最大区别:
watchEffect:自动收集依赖(用到了什么就监听什么)watch:需要你手动指定依赖源
内部依靠:
ReactiveEffect(effect 实例)track() / trigger()(依赖收集 / 触发机制)cleanupEffect()(清理旧副作用)
🎯 二、深度原理:watchEffect 内部到底做了什么?
当你写:
scss
watchEffect(() => {
console.log(obj.count)
})
Vue 做了以下事情:
1️⃣ 创建一个 ReactiveEffect 实例
php
const effect = new ReactiveEffect(fn, scheduler)
ReactiveEffect 是响应式系统的核心:
- 记录当前正在运行的副作用
- 维护依赖集合 deps
- 控制执行时机
2️⃣ 立即执行 fn(与 watch 不同)
watchEffect 不需要指定依赖
它会先运行一次回调,这一步非常关键:
scss
effect.run()
3️⃣ 执行 fn 时访问响应式数据 → track()
当执行 obj.count 时:
scss
track(obj, 'count')
Vue 将:
- effect 记录到 obj.count 的依赖列表中
- obj.count 也记录它依赖的所有 effect
完整结构是这样:
css
depMap: {
obj: {
count: [effect]
}
}
4️⃣ 当 count 改变时 → trigger()
当你写:
obj.count++
Vue 内部执行:
scss
trigger(obj, 'count')
找到所有依赖(effect),并调度执行。
5️⃣ 调度执行(scheduler)
watchEffect 会给 effect 注入一个 scheduler:
scss
scheduler = () => queueEffect(effect.run)
作用:
- 批量执行(避免重复 run)
- nextTick 后执行
- 控制执行顺序
6️⃣ cleanup 清理机制
每次执行 effect 前:
scss
cleanupEffect(effect)
作用:
- 删除 effect 对旧依赖的绑定
- 避免死循环
- 多次执行会重新收集依赖
你可以自己写收集变化依赖:
scss
watchEffect(() => {
if (flag.value) {
console.log(a.value)
} else {
console.log(b.value)
}
})
第一次访问 a
第二次访问 b
cleanupEffect 会清理旧 a 的依赖。
🎯 三、源码级流程图(强烈推荐背)
scss
watchEffect(fn)
↓
ReactiveEffect(fn)
↓
effect.run()
↓(运行 fn)
访问响应式数据 → track(effect)
↓
数据变化 → trigger()
↓
scheduler(effect)
↓
cleanupEffect()
↓
effect.run()(重新执行)
这是 Vue3 响应式系统最重要的流程图之一。
🎯 四、真实示例:自动依赖切换
scss
const flag = ref(true)
const a = ref(1)
const b = ref(2)
watchEffect(() => {
console.log(flag.value ? a.value : b.value)
})
执行顺序:
- 初次执行 → 访问 a → 收集 a 的依赖
- flag 改成 false,重新执行 → 清理 a 依赖 → 收集 b 依赖
- a.value++ → 不会触发 effect
- b.value++ → 会触发 effect
问到这里绝对是高分。
🎯 五、常见面试官追问(附答法)
❓1:watchEffect 为什么能"自动知道依赖"?
因为执行回调时,Vue 会:
- 设置 activeEffect
- 每次读取响应式数据都会调用 track,自动把 activeEffect 存进去
换句话讲:
effect 的执行 = 顺便收集依赖
❓2:watchEffect 和 computed 的内部关系是什么?
回答要重点强调:
- 二者都是 ReactiveEffect
- computed 是 lazy effect(只有访问 value 才执行)
- watchEffect 是 active effect(立即执行)
❓3:为什么 watchEffect 有 cleanup?
因为执行完一次后,下一次依赖可能改变
例如条件分支:
perl
if (flag) use a
else use b
没有 cleanup 会导致:
- 依赖污染
- 不必要的触发
- 内存泄漏
❓4:watchEffect 会不会死循环?
会,如果你这样写:
scss
watchEffect(() => {
obj.count++
})
因为执行 effect 会再次改数据,又触发 effect。
避免方法:
- 使用 watch
- 在回调内部不要直接修改依赖源
🎯 六、金牌总结(面试官非常喜欢听)
watchEffect = 自动依赖追踪 + 自动清理 + scheduler 调度执行。
执行时会访问哪些数据,就会被自动收集为依赖;依赖变化后重新执行 effect。
核心是 ReactiveEffect + track/trigger 机制,是 Vue3 响应式系统最重要的组成部分之一。