Published on

React 初始挂载渲染管线

基础架构:Fiber

Fiber 是 React 16+ 版本协调算法 (Reconciliation Algorithm) 的核心实现。它将原先不可中断的递归式 Virtual DOM diffing,重构为了一个可中断、可恢复的、基于链表的工作单元处理过程。

Fiber 的核心数据结构

  • 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 />) 触发。

  1. createRoot(rootNode): 创建一个 FiberRootNode 作为应用的根,并生成一个与之关联的、作为当前 Fiber 树根节点的 HostRoot FiberNode。
  2. root.render(<App />):
    • 创建一个更新对象 (Update object),并将 <App /> 这个 React 元素作为其 payload
    • 将这个更新对象加入到 HostRoot FiberNode 的更新队列 (update queue) 中。
    • 调用 scheduleUpdateOnFiber(),开始调度流程。
触发初始化的代

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 节点,并为需要变更的节点打上相应副作用标记(如 PlacementUpdateDeletion)。
  • 自定义组件处理: 当当前节点是函数组件或类组件(如 <App /> 对应的 Fiber 节点)时,beginWork 会执行组件函数或 render 方法,获取返回的 JSX。然后再次调用 reconcileChildren 为这些子元素创建或复用 Fiber 节点,挂到组件 Fiber 下。
  • HostComponent 处理: 当处理到 <div> 等原生 DOM 元素时,beginWork 会继续为其子元素创建 Fiber 节点。

completeWork: 向上完成

当一个 Fiber 节点的所有子节点都处理完毕后(即遍历到达叶子节点),completeWork 函数会被调用,开始“向上归并”的过程。

  1. 对于 HostComponentHostText 类型的 Fiber,在内存中创建真实的 DOM 节点,并将其赋值给 fiber.stateNode 属性。
  2. 处理 props,将属性(如 className, style)和事件监听器应用到内存中的 DOM 节点上。
  3. 将当前 Fiber 节点的副作用(flags)冒泡合并到其父节点,最终在根节点上形成一个完整的副作用链表,等待提交阶段处理。

阶段四:提交 (Commit) - 将变更应用到 DOM

当整个 Fiber 树的 beginWorkcompleteWork 流程都完成后,React 就拥有了一棵完整的 workInProgress 树和一份详尽的副作用列表。提交阶段便是将这些计算结果,同步地、不可中断地应用到真实 DOM 上。

  • 入口: commitRoot 函数。
  • commitMutationEffects(): 核心函数,它遍历副作用列表,并根据 flags 执行所有 DOM 的增、删、改操作。
Placement 标识

对于初始挂载,几乎所有 Fiber 节点都被标记了 PlacementcommitPlacement 函数会负责调用 appendChild 等 DOM API,将 completeWork 阶段在内存中创建好的 DOM 节点,一次性地插入到真实的页面中。