Vue 响应式系统源码剖析:从 effect、track 到 trigger 的运行机制

HTMLPAGE 团队
18 分钟阅读

从 Vue 响应式系统的核心数据结构与执行流程出发,讲清 effect、track、trigger、computed 与 scheduler 如何协同工作,帮助前端工程师把“会用响应式”推进到“理解为什么会更新”。

#Vue #Reactivity #Source Code #Composition API #Frontend

Vue 响应式系统源码剖析:从 effect、track 到 trigger 的运行机制

很多人会用 Vue 的响应式 API,但当项目开始出现“为什么这里更新了、那里没更新”“为什么 computed 没按预期触发”“为什么 watch 会多跑一次”时,仅靠使用层经验已经不够了。你需要理解 Vue 到底是如何记录依赖、触发更新、调度副作用的。

Vue 响应式系统的核心价值,不是让数据自动更新 UI,而是把“谁依赖了谁”这件事变成可追踪的运行时关系。


1. 整体心智模型:响应式本质上是依赖收集 + 触发通知

可以先把核心流程压缩成三步:

  1. 在读取响应式数据时记录依赖
  2. 在修改响应式数据时找到相关副作用
  3. 按调度策略重新执行副作用

Vue 把这三件事拆成几个关键概念:

  • reactive / ref:提供可追踪的数据源
  • effect:包裹会依赖响应式数据的副作用函数
  • track:读取时收集依赖
  • trigger:写入时触发依赖

2. 为什么要靠 Proxy

Vue 3 选择 Proxy,是因为它能在属性读取和写入时拦截行为。读取时做 track,写入时做 trigger

这意味着:

  • 你不需要手动声明依赖关系
  • 系统可以在运行时知道某个属性被谁读过
  • 也正因为如此,依赖只有在“真正读取时”才会建立

这也是很多初学者困惑的来源:不是“定义了响应式对象就自动一切可追踪”,而是“副作用执行期间读到了什么,系统才知道该追踪什么”。


3. effect:响应式系统真正调度的是副作用

最核心的不是数据本身,而是 effect。因为 UI 更新、computed 计算、watch 回调,本质上都是不同形式的副作用函数。

简化后的理解:

effect(() => {
  console.log(state.count)
})

执行这段时:

  1. 当前 effect 被标记为 active
  2. 运行函数,读取 state.count
  3. track 把这个 active effect 记录到 count 的依赖集合里
  4. 以后 count 变化时,trigger 就能找到它

所以响应式系统真正维护的是“属性 -> effect 集合”的关系网。


4. track 与 trigger 的关键数据结构

可以把 Vue 的依赖图理解成这样的层次:

  • WeakMap:按 target 分类
  • Map:按 key 分类
  • Set:保存依赖这个 key 的 effect

这种结构的好处是:

  • 对象可被垃圾回收
  • 每个属性单独维护依赖
  • 同一个 effect 不会重复收集

理解这一点后,你就会知道为什么“只改某个 key,只有相关 effect 会重跑”,而不是整个对象的所有使用点都无脑刷新。


5. computed 为什么既像值,又像 effect

computed 的特别之处,在于它本身也是副作用,但带缓存。它会:

  • 在依赖变化后把自己标记为 dirty
  • 只有真正再次读取时才重新计算

这让它很适合承接“派生状态”。如果一个表达式依赖多个响应式源,但并不需要每次变动都立刻执行真实副作用,computed 往往比 watch 更稳。


6. scheduler 解释了“为什么不是立刻更新”

Vue 不会在每次数据变化时都同步重跑所有 effect,否则同一轮更新里连续写多个状态会非常低效。scheduler 的价值,就是把更新放进更合理的调度时机。

这也是为什么你会看到:

  • 多次同步修改状态,DOM 往往只更新一次
  • watch 有不同 flush 时机
  • 某些 effect 不是立即执行,而是进入任务队列

如果不了解调度层,就很难真正读懂“为什么这个值现在变了,但界面还没刷新”。


7. 失败案例:把 watch 当成万能响应式工具

很多项目响应式变复杂以后,会出现一个典型问题:

  1. 哪儿需要联动就加一个 watch
  2. watch 套 watch,副作用链越来越长
  3. 更新顺序开始难以预测

根因不是 Vue 响应式有问题,而是把 effect、computed、watch 的职责混用了。

一个更稳的分工通常是:

  • 派生值用 computed
  • 业务副作用用 watch
  • 组件渲染交给渲染 effect

8. Checklist:理解响应式源码后,实战里要记住什么

  • 依赖只会在 effect 执行期间读取时建立
  • 属性级追踪意味着更新是精细化的,不是全量刷
  • computed 是带缓存的 effect,不是普通变量
  • scheduler 决定更新时机,不要默认一切同步发生
  • watch 适合副作用,不适合滥用为所有联动逻辑

9. 结论:理解响应式源码,是为了更好地控制复杂状态

Vue 响应式系统并不神秘,它本质上是在运行时建立一张依赖图,然后用调度器在正确时机重跑副作用。理解 effect -> track -> trigger -> scheduler 这条链路后,你会更容易判断状态逻辑该放哪儿、为什么会更新、以及为什么某些更新不该发生。

进一步阅读: