Vue 响应式核心机制依赖流转图
Vue3 响应式核心机制(effect / track / trigger / cleanup / scheduler)
整体架构
Vue 响应式系统本质上是:
用
effectFn作为桥梁,把数据的getter(track)和setter(trigger)连接起来,形成一个自动更新的闭环系统。
核心流程图
┌──────────────────────┐
│ effect(fn) │
│ 创建响应式副作用函数 │
└─────────┬────────────┘
│
▼
┌──────────────────────┐
│ effectFn() │
│ (包装后的执行函数) │
└─────────┬────────────┘
│
│ 设置:
│ activeEffect = effectFn
▼
┌────────────────────────────────────┐
│ 执行用户 fn │
│ fn() 业务逻辑 │
└──────────────┬─────────────────────┘
│ 访问响应式数据
▼
┌────────────────────────────────────┐
│ track │
│ 依赖收集(getter触发) │
│ │
│ target -> key -> deps(Set) │
│ deps.add(activeEffect) │
│ activeEffect.deps.push(deps) │
└──────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 依赖结构建立完成 │
│ effectFn <-----> deps(Set) │
└────────────────────────────────────┘
========================================================
数据发生变化
========================================================
obj.foo = newValue
│
▼
┌────────────────────────────────────┐
│ trigger │
│ setter触发更新 │
└──────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 找到依赖集合 deps(Set) │
│ effects = [effectFn1, effectFn2] │
└──────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────┐
│ scheduler / 执行调度 │
│ │
│ const effectsToRun = new Set() │
│ filter activeEffect │
└──────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 执行副作用函数 │
│ effectsToRun.forEach(effectFn) │
│ │ │
│ ▼ │
│ cleanup(effectFn) │
│ 删除旧依赖关系 │
│ │
│ effectFn() 再执行 fn() │
└──────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 重新 track 收集 │
│ 形成新的依赖图 │
└────────────────────────────────────┘
五大核心模块
| 模块 | 作用 |
|---|---|
| effect | 注册副作用函数 |
| track | 收集依赖 |
| trigger | 触发依赖 |
| cleanup | 清理旧依赖 |
| scheduler | 调度执行策略 |
1. effect
用户代码
effect(() => {
console.log(obj.foo)
})
内部实现(简化版)
function effect(fn) {
const effectFn = () => {
cleanup(effectFn)
activeEffect = effectFn
fn()
}
effectFn.deps = []
effectFn()
}
为什么要包装一层 effectFn
因为 Vue 需要给副作用函数增加运行时能力:
effectFn.deps = []
用于记录:
- 当前 effect 依赖了哪些 deps 集合
同时还需要:
- cleanup
- scheduler
- activeEffect
- trigger 重新执行
因此:
fn -> 用户业务逻辑
effectFn -> Vue管理的响应式执行单元
2. track(依赖收集)
当读取响应式数据时:
console.log(obj.foo)
触发:
track(target, key)
核心逻辑:
function track(target, key) {
if (!activeEffect) return
deps.add(activeEffect)
activeEffect.deps.push(deps)
}
建立双向关联:
fooSet ------> effectFn
↑
|
effectFn.deps
3. trigger(触发更新)
当数据修改时:
obj.foo++
触发:
trigger(target, key)
核心逻辑:
const effects = depsMap.get(key)
找到:
foo
├── effectFn1
├── effectFn2
└── effectFn3
然后准备执行这些 effect。
4. cleanup(清理旧依赖)
为什么需要 cleanup
看下面例子:
effect(() => {
if (obj.ok) {
console.log(obj.foo)
}
})
第一次:
obj.ok = true
依赖:
ok -> effectFn
foo -> effectFn
后来:
obj.ok = false
再次执行后:
console.log(obj.foo)
已经不会执行。
因此:
foo -> effectFn
这条依赖必须删除。
cleanup 实现
function cleanup(effectFn) {
effectFn.deps.forEach((deps) => {
deps.delete(effectFn)
})
effectFn.deps.length = 0
}
cleanup 做了什么
第一步
deps.delete(effectFn)
删除:
属性 -> effect
关系
例如:
fooSet
└── effectFn
第二步
effectFn.deps.length = 0
删除:
effect -> 属性
关系
例如:
effectFn
├── fooSet
└── barSet
为什么不是重复操作
依赖关系是双向的:
fooSet <------> effectFn
barSet <------> effectFn
cleanup 后:
fooSet effectFn
barSet
因此两步缺一不可。
5. scheduler(调度器)
trigger 时并不一定立即执行 effect。
Vue 会先调度:
scheduler(effectFn)
常见用途:
去重
const jobQueue = new Set()
避免同一个 effect 多次执行。
批量更新
Promise.resolve().then(flushJobs)
把多次更新合并为一次执行。
控制执行时机
例如:
- 同步执行
- 微任务执行
- 宏任务执行
为什么 trigger 要创建 effectsToRun
源码:
const effectsToRun = new Set()
effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
effectsToRun.forEach(effectFn => effectFn())
原因1:避免遍历时修改原集合
如果直接:
effects.forEach(effectFn => effectFn())
执行过程中:
cleanup(effectFn)
会:
deps.delete(effectFn)
修改当前正在遍历的 Set。
可能导致:
- 重复执行
- 漏执行
- 无限循环
原因2:过滤当前正在执行的 effect
避免:
effect(() => {
obj.foo++
})
这种情况出现无限递归。
activeEffect 的作用
let activeEffect
表示:
当前正在收集依赖的 effect
例如:
activeEffect = effectFn
那么:
track()
就知道应该收集谁。
effectFn、activeEffect、deps 的关系
activeEffect
│
▼
effectFn
│
┌──────────┴──────────┐
▼ ▼
fooSet barSet
▲ ▲
└──────────┬──────────┘
│
deps
响应式闭环
effect(fn)
│
▼
track()
│
▼
建立依赖关系
│
▼
trigger()
│
▼
scheduler()
│
▼
cleanup()
│
▼
重新执行 effectFn
│
▼
重新 track()
│
▼
形成新的依赖关系
核心记忆
三个核心对象
effectFn -> 响应式执行单元
deps(Set) -> 依赖集合
activeEffect -> 当前依赖收集指针
两个核心阶段
收集阶段
effect
↓
track
建立:
属性 -> effect
关系
更新阶段
trigger
↓
scheduler
↓
cleanup
↓
effect重新执行
更新依赖关系。
一句话总结
Vue 响应式系统的本质就是:
effect 负责注册副作用函数,track 负责建立依赖关系,trigger 负责触发更新,cleanup 负责移除过期依赖,scheduler 负责控制执行时机,最终形成一个自动追踪依赖并响应更新的闭环系统。

浙公网安备 33010602011771号