一、概念
在 React 源码中, React Reconciler(协调器) 是整个 React 核心机制的中枢,它不仅负责 Fiber 树的构建与更新,还集成了调度( 由 Scheduler 支持 )、副作用处理、错误边界等关键能力。
React Reconciler 的核心是 Fiber 架构 ,通过双缓存、可中断的调和阶段、优先级调度和副作用标记,实现了高效的 UI 更新。
- 不要假设 render 阶段的执行次数 : 可能被中断、重试多次
- 副作用只能放在
useEffect/useLayoutEffect: render 阶段必须是纯函数 - 优先使用 Concurrent Root :
createRoot而非Render,才能启用 Suspense 、 Transition 、 Offscreen 等新特性 - 避免在 render 中创建新对象/函数 : 会破坏 bailout 优化
- 理解 Lane 模型 :它是控制更新优先级和批处理的核心
1. 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 节点既是数据载体,也是工作单元,支撑了并发渲染、优先级调度等核心特性。
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 链表的深度优先遍历(
child➞sibling➞return)
- 可中断 : 通过
- 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 会覆盖低优先级的 (如紧急更新覆盖慢速渲染)
- 不通类型的更新(如
setState、foreUpdate、Suspense回退)会被分配不同的优先级 - 低优先级任务可能被“降级”(如从
Update降级为Lazy),避免阻塞用户交互
5. 副作用标记( Effect Tag )
每个 Fiber 节点的 effectTag 字段阶段需要在 Commit 阶段执行的操作类型,常见类型标记标记:
Placement: 插入新节点Update: 更新现有节点Deletion:删除节点Hydrating:水合阶段的特殊标记
Render 阶段手机 effectTag ,避免直接操作宿主环境;Commit 阶段根据 effectTag 批量执行操作(如遍历所有带有 Placement 的 Fiber 节点,插入 DOM )。
6. 组件生命周期与 HOOK 的执行时机
- 类组件 :
render(): 在 Render 阶段执行,生成虚拟 DOMcomponentDisMount/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 getDerivedStateFromError 或 componentDidCatch 定义 ),用于捕获子树中的渲染错误,并渲染降级内容。
- 当组件在 Render 阶段抛出错误时, Reconciler 会向上遍历父节点,找到最近的错误边界组件
- 错误边界组件的 Fiber 节点会被标记为需要更新(
effectTag包含Update),并在 Commit 阶段调用getDerivedStateFromError更新状态,最终渲染降级内容
- 错误边界无法捕获异步(如
setTimeout)、事件处理函数或服务端渲染的错误 - 若没有错误边界,为捕获的错误将导致整个应用的崩溃
8. 不可变数据与状态更新
React 依赖状态的 不可变性( Immutability ) 判断是否需要更新。
- 类组件的 setState 合并新状态时,通过浅比较
prevState和this.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 等),以及与该环境交互的适配层(如 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),实现了跨平台能力(“一次编写,多平台运行”)
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 节点(可能隐藏)和状态(如
useState、useRef),避免重复计算。当组件变为可见时,只需调整样式(如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 通过可中断的调和过程,优先渲染可见内容,挂起未准备好的子树,提升应用响应速度