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 负责控制执行时机,最终形成一个自动追踪依赖并响应更新的闭环系统。

posted @ 2026-06-10 20:56  Li_pk  阅读(9)  评论(0)    收藏  举报