跳到主要内容

概念​

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

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

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

Fiber 架构​

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

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

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

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

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

Reconciler 维护两颗 Fiber 树:

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

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

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

调和两个阶段​

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

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

优先级调度​

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

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

副作用标记( Effect Tag )​

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

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

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

组件生命周期与 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 会阻塞浏览器绘制,需谨慎使用(仅在需要同步修正布局时使用)

错误处理:错误边界( Error Boundary )​

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

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

不可变数据与状态更新​

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

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

并发特性( 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 主线程支持“列队任务 + 事件循环”

Host(宿主环境)​

Host 指 React 运行的具体平台环境(如浏览器 DOM 、 [React Native] 原生组件 、 Canvas 等),以及与该环境交互的适配层(如 ReactDomHostConfig 或 ReactNativeHostConfig )。是 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 提供的方法(如 createInstance 、 commitUpdate ),实现了跨平台能力(“一次编写,多平台运行”)

Hidden(隐藏状态)​

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

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

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

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

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

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),使交互功能生效

Offscreen (离屏渲染)​

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

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

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

Suspense (挂起)​

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

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

渲染流程​

收集副作用 + 中断检查​