【设计理念】
架构的演进
让我们从一个很简单的功能着手:
tsx
点击上面的按钮,视图上显示的值会加 1。
让我们看看 React15 是如何思考上面的功能的:
- 通过一段描述(即 JSX)来告诉 React 你想要渲染怎样的 DOM 节点
- 让 JSX 翻译成 虚拟 DOM,并交给 Renderer(渲染器)将虚拟 DOM 渲染成节点(mount 阶段)
- 点击按钮后,通知 Reconciler(协调器)视图需要更新
- Reconciler 内部产生新的虚拟 DOM,并将新旧虚拟 DOM 对比(Diff 算法)并提取出发生变化的部分。
- 将产生差异的虚拟 DOM 交给 Renderer,Renderer 更新浏览器视图上的 DOM 节点
一张图表示 React15 的架构:

这个架构下,当数据不断更新时(假设用户不断点击按钮),会按照顺序做以下事情:
- Reconciler 用 Diff 算法计算出需要更新的范围,并将结果提交给 Renderer
- Renderer 根据差异结果更新真实的 DOM 节点
- 浏览器视图发生变化
- ... 上面的过程不断循环
整个处理过程像单一的队列般有序,最明显的是,整个过程是同步处理的。
这也意味着 React 的架构遇到了类似于 HTTP 的队首阻塞问题——一旦有一个地方产生了阻塞(用户的 input 行为造成大面积的视图更新时),浏览器忙于处理这些数据层面的运算导致页面卡顿。
而这个问题,使用debounce和throttle都不能很好地解决。
最佳的解决方式是异步,异步能通过优秀的调度来避免阻塞——浏览器已经证明了这一点。
在浏览器内部的异步 Schedule 中,采用双队列来循环调度 JS 代码的执行并协调视图的更新。
为了达成异步更新以提高 React 的性能,React 重写源码升级了新的架构,新架构下 React 实现了一个异步可中断的 Scheduler(调度器)。

新架构下的更新规则变成了:
- Scheduler 判断更新等级,是否有更高的优先级需要被调度
- 如果没有则将更新交给 Reconciler
- Reconciler 接受到更新并创建新的虚拟 DOM 树与旧的虚拟 DOM 做对比
- 对比完成后给需要更新的 DOM 打上 Update 标记交给 Renderer
- Renderer 更新视图上的真实 DOM
在新架构中,Scheduler 负责接受更新任务,并对更新任务的优先级进行排序。
当把 A 更新推送给 Reconciler 时,Reconciler 会进行 Diff 运算。在这过程中如果 Scheduler 又接收到一个更高级的 B 更新,则中断处理 A 更新的计算,转而再去计算 B 更新。
Fiber 架构
Fiber 是 React16 重写代码后产生的一种架构,它有三种含义:
-
新版本的 Reconciler 也可以称为 Fiber Reconciler

-
Fiber 作为静态数据结构,它保存各个节点的虚拟 DOM
以下面代码为例:
js1import React, { useState } from 'react' 2 3export default function App() { 4 const [state, setState] = useState(0) 5 return ( 6 <div> 7 <button onClick={() => setState((n) => n + 1)}>click me</button> 8 <div>{state}</div> 9 </div> 10 ) 11} 12ReactDOM.render(<App />, document.getElementById('root'))在 React 内部生产的 Fiber 节点的数据结构是这样的

- 首先会生成一个根
FiberRootNode ReactDOM.render会生成RootFiber节点,一个应用中可能存在多个RootFiber- 从 RootFiber 开始,每个父节点的 child 属性保存着子节点的信息
- 到 RootFiber 为止,每个子节点的 return 属性保存着父节点的信息
- 从
FiberRootNode往下是递的过程,从最后的子节点往上则是归的过程
- 首先会生成一个根
-
作为动态的工作单元保存着 Hooks 的副作用和状态等信息
Fiber 工作原理
Fiber 架构采用双缓存来保存生成的虚拟 DOM,以下图为例

当ReactDOM.render首次挂载组件时,React 会创建一个RootFiber节点,并让 FiberRootNode 的 current 属性指向 RootFiber。
当首屏渲染时,React 会创建右侧的 RootFiber,并且递归返回第一批虚拟 DOM 节点。该虚拟 DOM 节点的 RootFiber 与第一个 RootFiber 节点之间用 alternate 属性连接。
当第二个 RootFiber 正在构建时,这个虚拟 DOM 树称为WorkInProgress Fiber 树。
FiberRootNode 的 current 指向的 Fiber 树称为current Fiber树。

当 workInProgress树完成后,FiberRootNode会将 current 指向它的 RootFiber节点,此时它变成了current Fiber 树。

接下来是点击 P 标签触发更新的逻辑。当用户点击 P 标签时,会基于 current Fiber 树的 RootFIber 的 alternate 指向的那个 Fiber 树创建一棵新的 workInProgress Fiber 树。

JSX 调用后返回的数据结构与 current Fiber 树做对比并构建出 workInProgress Fiber 树的算法叫做 Diff 算法。
当 workInProgress 树构建完成后,FiberRootNode 的 current 又指向它,它又会变成 current Fiber 树。

每个 Fiber 节点之间都会用 alternate 属性做连接。