Appearance
总结:串联 effect、scheduler,构建完整的响应式系统视图
经过前面章节的分析,我们已经分别探讨了 Vue 响应式系统的核心组成部分:reactive 的代理、ref 的包装、computed 的缓存、watch 的侦听,以及 track/trigger 机制。
本节将把这些部分串联起来,从宏观角度展示它们是如何协同工作,构成一个完整的响应式系统。
响应式系统的五个核心角色
我们可以将整个响应式系统分为五个关键角色,每个角色都有明确的职责:
代理 (
Proxy):- 实现:
reactive()、readonly()。 - 职责:作为数据的访问拦截器。当数据被读取 (get) 或写入 (set) 时,它会捕获这些操作。
- 实现:
副作用 (
ReactiveEffect):- 实现:
effect()、render()(组件渲染函数)、computed()、watch()。 - 职责:封装一个需要响应数据变化而重新执行的函数(如
render)。它是一个“订阅者”。
- 实现:
依赖收集 (
track):- 实现:一个在
get拦截中被调用的函数。 - 职责:当
Proxy拦截到get(读取)操作时,track负责查看“当前正在执行的副作用”(activeEffect),并将这个“订阅者”记录到被读取数据的“订阅列表”(targetMap)中。
- 实现:一个在
派发更新 (
trigger):- 实现:一个在
set拦截中被调用的函数。 - 职责:当
Proxy拦截到set(写入)操作时,trigger负责查询“订阅列表”,找出所有订阅了该数据的“订阅者”(ReactiveEffect),并通知它们重新执行。
- 实现:一个在
调度器 (
scheduler):- 实现:
ReactiveEffect上的一个可选函数。 - 职责:
trigger在通知“订阅者”时,不会直接执行它,而是将执行权交给了scheduler。调度器(如queueJob)负责将任务进行去重、排序,并放入微任务队列(nextTick)中,实现高效的批量异步更新。
- 实现:
串联:一个完整的响应式数据流
我们以“组件更新”这一最常见的场景,将这五个角色串联起来。
场景:组件渲染了 state.count ,然后用户点击按钮执行了 state.count++。
数据流向:[数据变更] -> [1. 代理(set)] -> [4. 派发更新(trigger)] -> [5. 调度器(scheduler)] -> [2. 副作用(effect.run)] -> [UI 更新]
第一阶段:组件挂载(依赖收集)
副作用准备: 组件mount时,它的render函数会被封装成一个ReactiveEffect(renderEffect),并立即执行renderEffect.run()。renderEffect.run()会将自身实例赋值给全局的activeEffect变量,标记“当前有effect正在运行”。代理拦截get:render函数执行,访问state.count。reactive对象的Proxy拦截了get操作。track登记:get拦截器调用track()。track检查到activeEffect(即renderEffect)存在,于是将renderEffect登记到state.count的“订阅列表”(targetMap)中。run()完成:render执行完毕,activeEffect被清除(设为undefined)。renderEffect现在开始“订阅”state.count的变化。
第二阶段:数据变更(派发更新)
数据变更: 用户点击按钮,执行
state.count++。代理拦截set:Proxy拦截了set操作。trigger通知:set拦截器调用trigger()。trigger查询state.count的“订阅列表”,找到了renderEffect。移交
调度器:trigger并不直接调用renderEffect.run()。而是调用renderEffect在创建时被指定的scheduler。在 Vue 组件中,这个scheduler就是queueJob。调度器调度:queueJob接收到renderEffect任务,将其放入“异步更新队列”(一个Set)。如果在 1ms 内state.count被修改了 10 次,queueJob会确保renderEffect只入队一次(去重)。queueJob同时安排一个微任务(nextTick)来清空这个队列。副作用重新执行: 在浏览器同步代码执行完毕后,微任务开始执行。queueJob清空队列,renderEffect.run()被调用。循环:
renderEffect再次运行,回到“第一阶段”的第 2 步。它重新收集依赖(依赖清理机制确保了依赖的精确性),并生成新的 VNode,最终触发 DOM Diff 和界面更新。
总结
Vue 3 的响应式系统是一个解耦的、高效的系统:
reactive/ref(Proxy) 负责“拦截”变化。track/trigger负责“登记”和“通知”。ReactiveEffect负责“订阅”,它封装了“要做什么”(fn)。scheduler负责“何时做”,它将“通知”与“执行”解耦,实现了异步批量更新,这是 Vue 高性能的关键。
这套机制被 computed、watch 和组件渲染等所有功能复用,用一套统一的逻辑驱动了整个 Vue 框架。
