React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。

React15 架构

React15 架构可以分为两层:

  • Reconciler(协调器)—— 负责找出变化的组件
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上

React15 架构不能支撑异步更新以至于需要重构。

Reconciler(协调器)

我们知道,在React中可以通过this.setState、this.forceUpdate、ReactDOM.render等API触发更新。
每当有更新发生时,Reconciler 会做如下工作:

  • 调用函数组件 或 class 组件的 render 方法,将返回的 JSX 转化为虚拟DOM
  • 将虚拟 DOM 和上次更新时的虚拟 DOM 对比
  • 通过对比,找出本次更新中,变化的虚拟 DOM
  • 通知 Renderer 将变化的虚拟DOM渲染到页面上

React15 是 stack Reconciler,React16 是 fiber Reconciler

Renderer(渲染器)

由于React支持跨平台,所以不同平台有不同的Renderer。我们前端最熟悉的是负责在浏览器环境渲染的Renderer —— ReactDOM

除此之外,还有:

  • ReactNative渲染器,渲染 App 原生组件
  • ReactTest渲染器,渲染出纯 Js 对象用于测试
  • ReactArt渲染器,渲染到 Canvas, SVG 或 VML (IE8)

在每次更新发生时,Renderer 接到 Reconciler 通知,将变化的组件渲染在当前宿主环境。

React16 架构

React16架构可以分为三层:

  • Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler
  • Reconciler(协调器)—— 负责找出变化的组件
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上

React - 图1

Scheduler(调度器)

既然我们以浏览器是否有剩余时间作为任务中断的标准,那么我们需要一种机制,当浏览器有剩余时间时通知我们。

其实部分浏览器已经实现了这个API,这就是 requestIdleCallback。但是由于以下因素,React 放弃使用:

  • 浏览器兼容性
  • 触发频率不稳定,受很多因素影响。比如当我们的浏览器切换tab后,之前 tab 注册的 requestIdleCallback 触发的频率会变得很低

基于以上原因,React 实现了功能更完备的 requestIdleCallbackpolyfill,这就是 Scheduler
除了在空闲时触发回调的功能外,Scheduler 还提供了多种调度优先级供任务设置

react17 采用 MessageChannel 来实现 requestIdleCallback,当前环境不支持 MessageChannel 就采用 setTimeout

  1. //ReactFiberWorkLoop.old.js
  2. function workLoopConcurrent() {
  3. // Perform work until Scheduler asks us to yield
  4. while (workInProgress !== null && !shouldYield()) { //shouldYield判断是否暂停任务
  5. workInProgress = performUnitOfWork(workInProgress);
  6. }
  7. }

Scheduler 中的每个任务的优先级,使用过期时间expirationTime表示的。

  1. var expirationTime = startTime + timeout;
  1. 如果一个任务的过期时间离现在很近,说明它马上就要过期了,优先级很高。
  2. 如果过期时间很长,那它的优先级就低,没有过期的任务存放在 timerQueue 中,过期的任务存放在taskQueue中。

    timerQueuetimerQueue 都是小顶堆,所以 peek 取出来的都是离现在时间最近,即优先级最高的那个任务,然后优先执行它。

Reconciler(协调器)

Fiber Reconciler 是一个新尝试,致力于解决 stack reconciler 中固有的问题,同时解决一些历史遗留问题。Fiber reconciler 从 React 16 开始变成了默认的 reconciler。

它的主要目标是:

  • 能够把可中断的任务切片处理。
  • 能够调整优先级,重置并复用任务。
  • 能够在父元素与子元素之间交错处理,以支持 React 中的布局。
  • 能够在 render() 中返回多个元素。
  • 更好地支持错误边界。

Renderer(渲染器)

由于React支持跨平台,所以不同平台有不同的Renderer。我们前端最熟悉的是负责在浏览器环境渲染的Renderer —— ReactDOM

除此之外,还有:

  • ReactNative渲染器,渲染 App 原生组件
  • ReactTest渲染器,渲染出纯 Js 对象用于测试
  • ReactArt渲染器,渲染到 Canvas, SVG 或 VML (IE8)

在每次更新发生时,Renderer 接到 Reconciler 通知,将变化的组件渲染在当前宿主环境。

React17

Scheduler 的改进

Lane 模型

React16 用expirationTime属性代表优先级,该优先级和 IO 不能很好的搭配工作(io 的优先级高于 cpu 的优先级)

React17 改用有更加细粒度的优先级表示方法,即 Lane 模型
Lane 用二进制位表示优先级,二进制中的 1 表示位置,同一个二进制数可以有多个相同优先级的位,这就可以表示“批”的概念,而且二进制方便计算。

⭐ Lane的二进制位如下,1 的 bits 越多,优先级越低

  1. //ReactFiberLane.js
  2. export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
  3. export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
  4. export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
  5. export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;
  6. export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
  7. const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;
  8. const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
  9. const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;
  10. export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;
  11. export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;
  12. const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;
  13. const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;
  14. const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;
  15. export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000;
  16. export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;
  17. const NonIdleLanes = /* */ 0b0000111111111111111111111111111;
  18. export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
  19. const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;
  20. export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;

参考资料

《react 源码解析》
《react 技术揭秘》