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(渲染器)—— 负责将变化的组件渲染到页面上
Scheduler(调度器)
既然我们以浏览器是否有剩余时间作为任务中断的标准,那么我们需要一种机制,当浏览器有剩余时间时通知我们。
其实部分浏览器已经实现了这个API,这就是 requestIdleCallback。但是由于以下因素,React 放弃使用:
- 浏览器兼容性
- 触发频率不稳定,受很多因素影响。比如当我们的浏览器切换tab后,之前 tab 注册的 requestIdleCallback 触发的频率会变得很低
基于以上原因,React 实现了功能更完备的 requestIdleCallback
polyfill,这就是 Scheduler。
除了在空闲时触发回调的功能外,Scheduler 还提供了多种调度优先级供任务设置
react17 采用 MessageChannel 来实现 requestIdleCallback,当前环境不支持 MessageChannel 就采用 setTimeout
//ReactFiberWorkLoop.old.js
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) { //shouldYield判断是否暂停任务
workInProgress = performUnitOfWork(workInProgress);
}
}
在 Scheduler 中的每个任务的优先级,使用过期时间expirationTime
表示的。
var expirationTime = startTime + timeout;
- 如果一个任务的过期时间离现在很近,说明它马上就要过期了,优先级很高。
- 如果过期时间很长,那它的优先级就低,没有过期的任务存放在
timerQueue
中,过期的任务存放在taskQueue
中。timerQueue
和timerQueue
都是小顶堆,所以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 越多,优先级越低
//ReactFiberLane.js
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;
export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;
const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;
export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;
const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;
const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;
export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000;
export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;
const NonIdleLanes = /* */ 0b0000111111111111111111111111111;
export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;
export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;