Vue 响应式系统源码剖析:从 effect、track 到 trigger 的运行机制
很多人会用 Vue 的响应式 API,但当项目开始出现“为什么这里更新了、那里没更新”“为什么 computed 没按预期触发”“为什么 watch 会多跑一次”时,仅靠使用层经验已经不够了。你需要理解 Vue 到底是如何记录依赖、触发更新、调度副作用的。
Vue 响应式系统的核心价值,不是让数据自动更新 UI,而是把“谁依赖了谁”这件事变成可追踪的运行时关系。
1. 整体心智模型:响应式本质上是依赖收集 + 触发通知
可以先把核心流程压缩成三步:
- 在读取响应式数据时记录依赖
- 在修改响应式数据时找到相关副作用
- 按调度策略重新执行副作用
Vue 把这三件事拆成几个关键概念:
reactive/ref:提供可追踪的数据源effect:包裹会依赖响应式数据的副作用函数track:读取时收集依赖trigger:写入时触发依赖
2. 为什么要靠 Proxy
Vue 3 选择 Proxy,是因为它能在属性读取和写入时拦截行为。读取时做 track,写入时做 trigger。
这意味着:
- 你不需要手动声明依赖关系
- 系统可以在运行时知道某个属性被谁读过
- 也正因为如此,依赖只有在“真正读取时”才会建立
这也是很多初学者困惑的来源:不是“定义了响应式对象就自动一切可追踪”,而是“副作用执行期间读到了什么,系统才知道该追踪什么”。
3. effect:响应式系统真正调度的是副作用
最核心的不是数据本身,而是 effect。因为 UI 更新、computed 计算、watch 回调,本质上都是不同形式的副作用函数。
简化后的理解:
effect(() => {
console.log(state.count)
})
执行这段时:
- 当前 effect 被标记为 active
- 运行函数,读取
state.count track把这个 active effect 记录到count的依赖集合里- 以后
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 当成万能响应式工具
很多项目响应式变复杂以后,会出现一个典型问题:
- 哪儿需要联动就加一个 watch
- watch 套 watch,副作用链越来越长
- 更新顺序开始难以预测
根因不是 Vue 响应式有问题,而是把 effect、computed、watch 的职责混用了。
一个更稳的分工通常是:
- 派生值用 computed
- 业务副作用用 watch
- 组件渲染交给渲染 effect
8. Checklist:理解响应式源码后,实战里要记住什么
- 依赖只会在 effect 执行期间读取时建立
- 属性级追踪意味着更新是精细化的,不是全量刷
- computed 是带缓存的 effect,不是普通变量
- scheduler 决定更新时机,不要默认一切同步发生
- watch 适合副作用,不适合滥用为所有联动逻辑
9. 结论:理解响应式源码,是为了更好地控制复杂状态
Vue 响应式系统并不神秘,它本质上是在运行时建立一张依赖图,然后用调度器在正确时机重跑副作用。理解 effect -> track -> trigger -> scheduler 这条链路后,你会更容易判断状态逻辑该放哪儿、为什么会更新、以及为什么某些更新不该发生。
进一步阅读:


