Skip to content

总结:串联 effect、scheduler,构建完整的响应式系统视图

经过前面章节的分析,我们已经分别探讨了 Vue 响应式系统的核心组成部分:reactive 的代理、ref 的包装、computed 的缓存、watch 的侦听,以及 track/trigger 机制。

本节将把这些部分串联起来,从宏观角度展示它们是如何协同工作,构成一个完整的响应式系统。

响应式系统的五个核心角色

我们可以将整个响应式系统分为五个关键角色,每个角色都有明确的职责:

  1. 代理 (Proxy)

    • 实现reactive()readonly()
    • 职责:作为数据的访问拦截器。当数据被读取 (get)写入 (set) 时,它会捕获这些操作。
  2. 副作用 (ReactiveEffect)

    • 实现effect()render() (组件渲染函数)、computed()watch()
    • 职责:封装一个需要响应数据变化而重新执行的函数(如 render)。它是一个“订阅者”。
  3. 依赖收集 (track)

    • 实现:一个在 get 拦截中被调用的函数。
    • 职责:当 Proxy 拦截到 get(读取)操作时,track 负责查看“当前正在执行的副作用”(activeEffect),并将这个“订阅者”记录到被读取数据的“订阅列表”(targetMap)中。
  4. 派发更新 (trigger)

    • 实现:一个在 set 拦截中被调用的函数。
    • 职责:当 Proxy 拦截到 set(写入)操作时,trigger 负责查询“订阅列表”,找出所有订阅了该数据的“订阅者”(ReactiveEffect),并通知它们重新执行。
  5. 调度器 (scheduler)

    • 实现ReactiveEffect 上的一个可选函数。
    • 职责trigger 在通知“订阅者”时,不会直接执行它,而是将执行权交给了 scheduler。调度器(如 queueJob)负责将任务进行去重、排序,并放入微任务队列nextTick)中,实现高效的批量异步更新。

串联:一个完整的响应式数据流

我们以“组件更新”这一最常见的场景,将这五个角色串联起来。

场景:组件渲染了 state.count ,然后用户点击按钮执行了 state.count++

数据流向:[数据变更] -> [1. 代理(set)] -> [4. 派发更新(trigger)] -> [5. 调度器(scheduler)] -> [2. 副作用(effect.run)] -> [UI 更新]


第一阶段:组件挂载(依赖收集)

  1. 副作用 准备: 组件 mount 时,它的 render 函数会被封装成一个 ReactiveEffectrenderEffect),并立即执行 renderEffect.run()

  2. renderEffect.run() 会将自身实例赋值给全局的 activeEffect 变量,标记“当前有 effect 正在运行”。

  3. 代理 拦截 getrender 函数执行,访问 state.countreactive 对象的 Proxy 拦截了 get 操作。

  4. track 登记get 拦截器调用 track()track 检查到 activeEffect(即 renderEffect)存在,于是将 renderEffect 登记到 state.count 的“订阅列表”(targetMap)中。

  5. run() 完成render 执行完毕,activeEffect 被清除(设为 undefined)。renderEffect 现在开始“订阅”state.count 的变化。

第二阶段:数据变更(派发更新)

  1. 数据变更: 用户点击按钮,执行 state.count++

  2. 代理 拦截 setProxy 拦截了 set 操作。

  3. trigger 通知set 拦截器调用 trigger()trigger 查询 state.count 的“订阅列表”,找到了 renderEffect

  4. 移交 调度器trigger直接调用 renderEffect.run()。而是调用 renderEffect 在创建时被指定的 scheduler。在 Vue 组件中,这个 scheduler 就是 queueJob

  5. 调度器 调度queueJob 接收到 renderEffect 任务,将其放入“异步更新队列”(一个 Set)。如果在 1ms 内 state.count 被修改了 10 次,queueJob 会确保 renderEffect 只入队一次(去重)。queueJob 同时安排一个微任务(nextTick)来清空这个队列。

  6. 副作用 重新执行: 在浏览器同步代码执行完毕后,微任务开始执行。queueJob 清空队列,renderEffect.run() 被调用。

  7. 循环renderEffect 再次运行,回到“第一阶段”的第 2 步。它重新收集依赖(依赖清理机制确保了依赖的精确性),并生成新的 VNode,最终触发 DOM Diff 和界面更新。

总结

Vue 3 的响应式系统是一个解耦的、高效的系统:

  • reactive / ref (Proxy) 负责“拦截”变化。
  • track / trigger 负责“登记”和“通知”。
  • ReactiveEffect 负责“订阅”,它封装了“要做什么”(fn)。
  • scheduler 负责“何时做”,它将“通知”与“执行”解耦,实现了异步批量更新,这是 Vue 高性能的关键。

这套机制被 computedwatch 和组件渲染等所有功能复用,用一套统一的逻辑驱动了整个 Vue 框架。


微信公众号二维码

Last updated: