- Published on
React 初始挂载渲染管线
基础架构:Fiber
Fiber 是 React 16+ 版本协调算法 (Reconciliation Algorithm) 的核心实现。它将原先不可中断的递归式 Virtual DOM diffing,重构为了一个可中断、可恢复的、基于链表的工作单元处理过程。
FiberRootNode: 整个 React 应用的根节点,包含了应用的元信息。其current属性指向当前已渲染的 Fiber 树 (Fiber Tree) 的根节点 (HostRoot)。FiberNode: Fiber 树中的每个节点都是一个 FiberNode。它是一个包含了组件所有信息的 JavaScript 对象。tag: 标识 Fiber 节点的类型,如FunctionComponent,ClassComponent,HostComponent(DOM 元素) 等。stateNode: 对于HostComponent,它指向该 Fiber 节点对应的真实 DOM 实例。child,sibling,return: 用于构建树形结构的指针,分别指向第一个子节点、下一个兄弟节点和父节点。flags(或subtreeFlags): 一个位掩码,用于标记该 Fiber 节点在提交 (Commit) 阶段需要执行何种操作(如插入、更新、删除)。lanes: 一个位掩码,表示该 Fiber 节点上待处理的更新的优先级。
阶段一:触发 (Trigger) - 创建更新任务
初始挂载由 ReactDOM.createRoot(rootNode).render(<App />) 触发。
createRoot(rootNode): 创建一个FiberRootNode作为应用的根,并生成一个与之关联的、作为当前 Fiber 树根节点的HostRootFiberNode。root.render(<App />):- 创建一个更新对象 (Update object),并将
<App />这个 React 元素作为其payload。 - 将这个更新对象加入到
HostRootFiberNode 的更新队列 (update queue) 中。 - 调用
scheduleUpdateOnFiber(),开始调度流程。
- 创建一个更新对象 (Update object),并将
import { createRoot } from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
// 这一步触发了整个初始挂载流程
root.render();
阶段二:调度 (Schedule) - 任务优先级排序
此阶段由 React 内部的 Scheduler 模块负责,其本质是一个优先级队列。
- 机制:
scheduleCallback()函数接收一个任务(如渲染)并将其根据优先级放入队列。Scheduler 的工作循环 (workLoop) 会确保高优先级的任务(如用户交互触发的更新)能够优先执行。 - 初始挂载的优先级: 对于初始挂载,React 会为其分配一个同步 (SyncLane) 的优先级。这意味着,虽然渲染流程的入口是
performConcurrentWorkOnRoot,但由于其优先级是同步的,它实际上会立即开始执行,而不会被其他任务中断。
阶段三:渲染 (Render) - 构建 Fiber 树
这是 React 最核心的协调 (Reconciliation) 阶段,其目的是构建出代表新 UI 状态的 Fiber 树,并找出需要对 DOM 进行的所有变更。
- 入口: 由
performConcurrentWorkOnRoot函数启动..
尽管现代 React 支持并发(可中断)渲染,但初始挂载默认是同步执行的。这是因为推迟初始 UI 的绘制对用户体验没有任何帮助。因此,performConcurrentWorkOnRoot 内部会调用 renderRootSync,进入一个同步的渲染循环。
- 工作循环 (
workLoopSync):renderRootSync的核心是一个循环,它通过workLoopSync函数,以深度优先遍历的方式,自顶向下地处理每一个 Fiber 节点。每个 Fiber 节点的处理过程被称为一个工作单元 (unit of work),由performUnitOfWork函数执行。
workInProgress树: 在渲染阶段,React 并不会直接修改当前已有的 Fiber 树(current树)。相反,它会基于current树创建一个副本,这棵新树被称为workInProgress树。所有的计算和变更都发生在这棵新树上。这种“双缓冲 (double buffering)”技术使得 React 可以在不影响已渲染 UI 的情况下,在后台准备新的渲染。- 输出: 渲染阶段的最终产物是一棵完整的
workInProgress树,以及一个包含了所有 DOM 变更指令的副作用列表 (effect list)。
beginWork: 向下协调
beginWork 函数负责处理当前正在工作的 Fiber 节点,为其创建或复用子 Fiber 节点。
HostRoot处理: 对于根节点,它会处理更新队列,获取到<App />,然后调用reconcileChildren来为<App />创建一个 Fiber 节点。
reconcileChildren 的工作模式- 初始挂载: 由于没有旧的 Fiber 树可供比较,
reconcileChildren内部会调用mountChildFibers。此函数不会尝试复用节点,而是直接为所有子元素创建全新的 Fiber 节点,并为它们标记上Placement副作用(表示需要在 DOM 中插入)。 - 更新: 在更新场景下,则会调用
reconcileChildFibers。该函数会对比新旧子节点列表(基于key),尽可能复用旧的 Fiber 节点,并为需要变更的节点打上相应副作用标记(如Placement、Update、Deletion)。
- 自定义组件处理: 当当前节点是函数组件或类组件(如
<App />对应的 Fiber 节点)时,beginWork会执行组件函数或render方法,获取返回的 JSX。然后再次调用reconcileChildren为这些子元素创建或复用 Fiber 节点,挂到组件 Fiber 下。 HostComponent处理: 当处理到<div>等原生 DOM 元素时,beginWork会继续为其子元素创建 Fiber 节点。
completeWork: 向上完成
当一个 Fiber 节点的所有子节点都处理完毕后(即遍历到达叶子节点),completeWork 函数会被调用,开始“向上归并”的过程。
- 对于
HostComponent和HostText类型的 Fiber,在内存中创建真实的 DOM 节点,并将其赋值给fiber.stateNode属性。 - 处理 props,将属性(如
className,style)和事件监听器应用到内存中的 DOM 节点上。 - 将当前 Fiber 节点的副作用(
flags)冒泡合并到其父节点,最终在根节点上形成一个完整的副作用链表,等待提交阶段处理。
阶段四:提交 (Commit) - 将变更应用到 DOM
当整个 Fiber 树的 beginWork 和 completeWork 流程都完成后,React 就拥有了一棵完整的 workInProgress 树和一份详尽的副作用列表。提交阶段便是将这些计算结果,同步地、不可中断地应用到真实 DOM 上。
- 入口:
commitRoot函数。 commitMutationEffects(): 核心函数,它遍历副作用列表,并根据flags执行所有 DOM 的增、删、改操作。
Placement 标识对于初始挂载,几乎所有 Fiber 节点都被标记了 Placement。commitPlacement 函数会负责调用 appendChild 等 DOM API,将 completeWork 阶段在内存中创建好的 DOM 节点,一次性地插入到真实的页面中。