跳到主要内容

一、概念

React 源码中, React Reconciler(协调器) 是整个 React 核心机制的中枢,它不仅负责 Fiber 树的构建与更新,还集成了调度( 由 Scheduler 支持 )、副作用处理、错误边界等关键能力。

React Reconciler 的核心是 Fiber 架构 ,通过双缓存、可中断的调和阶段、优先级调度和副作用标记,实现了高效的 UI 更新。

注意
  • 不要假设 render 阶段的执行次数 : 可能被中断、重试多次
  • 副作用只能放在 useEffect/useLayoutEffect : render 阶段必须是纯函数
  • 优先使用 Concurrent RootcreateRoot 而非 Render ,才能启用 Suspense 、 Transition 、 Offscreen 等新特性
  • 避免在 render 中创建新对象/函数 : 会破坏 bailout 优化
  • 理解 Lane 模型 :它是控制更新优先级和批处理的核心

1. Fiber 架构

Fiber 是 React Reconciler 的核心概念,本质是一个 工作单元( Work Unit ) ,对应组件树中的一个节点(如函数组件、类组件、 DOM 节点等)。

每一个 Fiber 节点都包含以下关键信息:

  • type : 组件类型( divMyComponent
  • tag : workTag (如 FunctionComponentClassComponentHostComponent 等)
  • stateNode :对应宿主实例 (如 DOM 节点 、 类组件实例)
  • memoizedState : 保存组件的状态(如 useState 的值、类组件的 state
  • effectTag : 副作用标记(如 PlacementUpdateDeletion ) ,记录需要在提交阶段执行的操作
  • alternate :指向双缓存中另一颗 Fiber 树的对应节点(用于快速复用节点)
  • child/sibling/return : 链表结构的子节点、兄弟节点、父节点指针(代替传统的属性结构的递归,支持可中断遍历)

Fiber 架构将 React 的调和过程从“不可中断的递归”改为“可中断的迭代”,通过链表遍历实现任务的拆分与调度。每个 Fiber 节点既是数据载体,也是工作单元,支撑了并发渲染、优先级调度等核心特性。

2. 双缓存( Double buffering ):高效更新的内存管理

Reconciler 维护两颗 Fiber 树:

  • current : 当前屏幕上显示内容对应的 Fiber 树(已提交到宿主环境)
  • workInProgress : 正在计算中的新的 Fiber 树(用于本次更新的临时树)

两棵树通过 alternate 属性双向连接( current.alternate === workInprogress ,反之亦然),复用节点以减少内存分配和垃圾回收压力。当 workInProgress 完成计算后,他会替代新的 current 树。

  • 双缓存避免了每次更新创建全新的 Fiber 树的开销,尤其在大规模应用中显著提升性能
  • workInProgress 树的计算是“试运行”( Dry Run ),若中途被中断(如更高优先级任务),可直接丢弃,不影响当前显示的 current 树

3. 调和两个阶段

Reconciler 的核心工作分为 Render 阶段Commit 阶段 , 二者职责与特性完全不同。

  • Render 阶段 : 通过 Diff 算法对比新旧 Fiber 树,标记需要更新的部分
    • 可中断 : 通过 requestIdleCallback 或 Scheduler 实现任务拆分,高优先级可打断当前渲染
    • 纯函数执行 : 不直接修改宿主环境( 如 DOM ),即计算差异并标记副作用
    • 遍历方式 : 基于 Fiber 链表的深度优先遍历( childsiblingreturn
  • Commit 阶段 : 将 Render 阶段标记为副作用并应用到宿主环境(如更新 DOM、 调用生命周期方法)
    • 不可中断 : 一旦开始,确保 UI 与状态最终一致
    • 同步进行 : 直接操作宿主环境(如 DOM 操作是同步的),可能会阻塞主线程(但 React 18 后通过并发模式优化)
    • 执行流程 :
      • DOM 更新(如插入、更新、删除节点)
      • 调用生命周期方法(类组件的 componentDidMount/componentDirUpdate ) 或 Hook( useLayoutEffect
      • 出发 useEffect 的调度(异步执行)

4. 优先级调度

React 通过 优先级( Priority ) 决定任务的执行顺序,高优先级任务(如用户输入)可中断低优先级任务(如列表渲染)。优先级模型在 React 18 后基于 Lane (车道)实现,代替了旧版的 Expiration Time 。

备注
  • Render 阶段的调和可被高优先级任务中断,保留当前优先进度(通过 workInProgress 树的中断点),后续从中断处恢复
  • Commit 阶段虽不可中断,但高优先级任务的 Commit 会覆盖低优先级的 (如紧急更新覆盖慢速渲染)
  • 不通类型的更新(如 setStateforeUpdateSuspense 回退)会被分配不同的优先级
  • 低优先级任务可能被“降级”(如从 Update 降级为 Lazy ),避免阻塞用户交互

5. 副作用标记( Effect Tag )

每个 Fiber 节点的 effectTag 字段阶段需要在 Commit 阶段执行的操作类型,常见类型标记标记:

  • Placement : 插入新节点
  • Update : 更新现有节点
  • Deletion :删除节点
  • Hydrating :水合阶段的特殊标记

Render 阶段手机 effectTag ,避免直接操作宿主环境;Commit 阶段根据 effectTag 批量执行操作(如遍历所有带有 Placement 的 Fiber 节点,插入 DOM )。

6. 组件生命周期与 HOOK 的执行时机

  • 类组件
    • render() : 在 Render 阶段执行,生成虚拟 DOM
    • componentDisMount/componentDidUpdate :在 Commit 阶段同步进行(依赖 effectTag 触发)
    • getSnapshotBeforeUpdate : 在 Commit 阶段 DOM 更新前执行,返回值传递给 componentDidUpdate
  • 函数组件与 HOOK
    • useState/useReducer : 在 Render 阶段读取状态, Commit 阶段更新(若需要)
    • useLayoutEffect : 在 Commit 阶段 DOM 更新后同步执行(类似 componentDidUpdate
    • useEffect :在 Commit 阶段结束后异步执行(通过 Scheduler )
注意
  • 避免在 render() 或 Hook 中执行副作用(如直接修改 DOM ),这些操作应放在生命周期或 useLayoutEffect/useEffect
  • useLayoutEffect 会阻塞浏览器绘制,需谨慎使用(仅在需要同步修正布局时使用)

7. 错误处理:错误边界( Error Boundary )

错误边界时类组件( 通过 static getDerivedStateFromErrorcomponentDidCatch 定义 ),用于捕获子树中的渲染错误,并渲染降级内容。

  • 当组件在 Render 阶段抛出错误时, Reconciler 会向上遍历父节点,找到最近的错误边界组件
  • 错误边界组件的 Fiber 节点会被标记为需要更新( effectTag 包含 Update ),并在 Commit 阶段调用 getDerivedStateFromError 更新状态,最终渲染降级内容
注意
  • 错误边界无法捕获异步(如 setTimeout )、事件处理函数或服务端渲染的错误
  • 若没有错误边界,为捕获的错误将导致整个应用的崩溃

8. 不可变数据与状态更新

React 依赖状态的 不可变性( Immutability ) 判断是否需要更新。

  • 类组件的 setState 合并新状态时,通过浅比较 prevStatethis.state 检测变化
  • 函数组件通过 useState 通过对比新旧值的引用( Object.is )决定是否触发调和
注意
  • 直接修改状态对象(如 state.obj.x = 1 ) 不会触发更新,因为引用未变
  • 不可变性是 React 优化的基础(避免深度比较),需配合 immer 等库简化不可变更新

9. 并发特性( Concurrent Features )

React 18 引入了并发模式( Concurrent Features )深度依赖 Reconciler 的 Fiber 结构,关键点包括:

  • 选择性更新( Selective Hydration` ) : 优先水合用户可见的部分,而非整个应用
  • 流式 SSR( Streaming SSR ) : 分块发送 HTML ,边传输边水合,提升首屏速度
  • 过渡更新( Transition ) : 将非紧急更新标记为低优先级(如搜索框输入后的列表更新),允许更紧急(如输入本身)打断它

Offscreen 和 Suspended 是 Concurrent Mode 的关键特性,体现了 React 从“同步渲染”向“可中断、优先级驱动“演进的设计思想。

信息

React并发 并不是指多线程并行,而是指“可中断、可恢复、带优先级的任务调度” -- 是一种逻辑上的并发( cooperative concurrency ),而非物理上的并行( parallelism )。

备注

JS 引擎是单线程,指的是“JS 主线程同一时间只能执行一个同步任务”,但不意味着所有的“操作都必须串行等待”。浏览器还有其他的线程(如渲染线程、网络线程、定时器线程),且 JS 主线程支持“列队任务 + 事件循环”

10. Host(宿主环境)

Host 指 React 运行的具体平台环境(如浏览器 DOM、 [React Native] 原生组件、 Canvas 等),以及与该环境交互的适配层(如 ReactDomHostConfigReactNativeHostConfig )。是 Reconciler 与平台 ( host environment ) 之间的桥梁,所有的 UI 最终都归结为 Host 节点。

备注
  • HostComponent : 表示平台原生元素(如 DOM 元素 <div><span> ,或 [React Native] 中的 View
  • HostText : 表示文本节点

Reconciler 本身并不直接操作 UI ,而是通过 Host 提供的接口完成实际渲染(如创建/更新/删除宿主节点)。Reconciler 在 commit 阶段会调用宿主配置( hostConfig ) 中的方法来操作真实的 DOM 。

  • 基础依赖 : Reconciler 的核心职责是计算虚拟 DOM 的差异( Fiber 树对比),但最终 UI 更新必须通过 Host 接口实现。例如,浏览器环境中 Host 负责操作 DOM, [React Native] 中负责操作原生组件
  • 解耦平台逻辑 : 通过抽象的 Host 接口, Reconciler 无需关心具体平台的实现细节,只需调用 Host 提供的方法(如 createInstancecommitUpdate ),实现了跨平台能力(“一次编写,多平台运行”)

11. Hidden(隐藏状态)

Hidden 指组件或其子树处于“不可见”的状态(如 CSS display: none 或父组件隐藏)。在 Reconciler 中, Hidden 可能表现为一种优化标记,指示该子树无需参与实际渲染流程,或仅保留基础结构而不更新内容。

在 Concurrent 模式下,用于实现 渐进式加载( progressive loading )骨架屏

允许 React 渲染内容但不立即显示(例如延迟显示低优先级内容)。

  • 渲染优化 : Reconciler 会识别 Hidden 组件,跳过其对子树的递归和 ( Reconciliation ),减少计算量。例如,如果一个组件被标记为 Hidden,其子树的 Fiber 节点可能被标记为 Offscreen ,仅保留必要状态而不参与布局或绘制
  • 状态保留 : 即使隐藏, React 可能保留组件的状态(如 useState 的值),以便其重新显示时快速恢复。 Reconciler 需通过标记 (如 effectTag )记录这种“隐藏但保留”的状态。
备注

HiddenComponent 在较新版本中已被 OffscreenComponent 取代或整合

12. Hydration (水合)

Hydration 是服务端渲染( SSR )或静态生成( SSG )的关键步骤,指客户端 React 将服务端返回的 HTML 字符串与客户端生成的虚拟 DOM 匹配,复用已有的 DOM 节点,并“激活”事件监听等交互能力的过程。

备注
  • 服务端先把 React 组件渲染成 HTML 字符串,发送给客户端;
  • 客户端拿到 HTML 后,不需要重新创建所有 DOM 节点(浪费性能),而是通过 Hydration 让现有 HTML “活” 起来 ——Reconciler 对比客户端虚拟 DOM 和服务端生成的 DOM,复用已有 DOM 结构,只绑定事件、恢复组件状态,让页面具备交互能力

对应 Fiber 类型, HostRoot 在 hydrate 模式下会进入 hydration 流程。

  • SSR 核心流程 :Reconciler 在 hydration 阶段会跳过部分常规的 DOM 创建逻辑,转而与服务端 HTML 字符串比对(通过 hydrateRoot 入口)。例如,当遇到 <div> 时,若服务端已存在该节点,Reconciler 会直接复用,而非重新创建,大幅提升 SSR 页面的客户端加载速度
  • 一致性校验 :Reconciler 需严格校验客户端与服务端渲染的一致性(如标签名、属性顺序),不一致时会抛出警告或错误,确保用户体验的可靠性
  • 事件绑定 :Hydration 阶段会为已存在的 DOM 节点附加事件处理器(如 onClick),使交互功能生效

13. Offscreen (离屏渲染)

Offscreen是 React 18 引入的实验性特性(现逐步稳定),允许组件在屏幕外预渲染(如缓存组件树)。其对应的 Fiber 节点会被标记为 Offscreen 模式,Reconciler 会保留其 DOM 结构或状态,当组件切换到可见时快速呈现。

与 Suspense 集成实现更精细的加载控制。

  • 渲染缓存 : Reconciler 会将 Offscreen 组件的 Fiber 子树标记为“离屏”,保留其 DOM 节点(可能隐藏)和状态(如 useStateuseRef ),避免重复计算。当组件变为可见时,只需调整样式(如 display: block ),无需重新调和
  • 动态可见性优化 :对于频繁切换可见性的组件(如模态框、侧边栏),Offscreen 减少了重复渲染的开销。 Reconciler 通过 Offscreen 标记管理子树的挂载/卸载状态,实现“按需渲染”

14. Suspense (挂起)

Suspense是 React 处理异步依赖(如数据加载、代码分割)的机制。当组件树中的某个子组件因异步操作未完成而“挂起”(Suspend)时,Suspense边界会捕获这一状态,并渲染指定的回退内容( Fallback )。

  • 在异步渲染控制 :Reconciler 在调和过程中遇到挂起的组件(如 useHook 加载数据时),会暂停当前子树的渲染,向上查找最近的 Suspense 边界,并切换到回退内容的渲染。这依赖于 Reconciler 对“挂起状态”的跟踪(如通过 SuspenseComponent 类型的 Fiber 节点)
  • 错误边界协同 :Suspense与错误边界( Error Boundary )共同构成 React 的异常处理体系。 Reconciler 需区分“挂起”(可恢复)和“错误”(需捕获)状态,分别触发 Suspense 回退或错误边界渲染
  • 并发模式支持 :Suspense是 React 并发模式( Concurrent Mode )的核心特性之一。Reconciler 通过可中断的调和过程,优先渲染可见内容,挂起未准备好的子树,提升应用响应速度

二、渲染流程

收集副作用 + 中断检查