专题知识学习:React Fiber

第一章:React Fiber 的诞生背景

1.1 React15 及之前版本的渲染瓶颈

递归不可中断的协调过程(stack reconciler栈协调器)

stack reconciler 是什么?

stack reconciler 是 React15 及之前版本中使用的虚拟 dom 协调算法,负责计算组件树的变化并更新真实 dom。其核心特点是基于递归遍历、不可中断的同步更新流程。

递归遍历虚拟 dom

采用深度优先遍历策略(FDS),从组件树根节点开始,递归调用组件的 render 方法,生成完整的虚拟 dom 树。递归遍历依赖于 JavaScript 调用栈(call stack),一旦开始就必须执行到底,无法中途暂停。

同步更新机制

所有更新(比如 setState)都会立即触发完整的 diff 计算,无法合并和延迟。当一个应用的组件树过于庞大时,一次递归 diff 计算是十分耗时的。stack reconciler 的工作流程分为两个阶段:

  • 协调(reconciliation):该阶段中采用递归 diff 算法,对比新旧虚拟 dom 树的差异,找到需要进行更新的树节点,标记需要更新的节点的增删改
  • 提交(commit):根据 diff 计算的结果,一次性同步执行所有的 dom 操作,并触发生命周期钩子(componentDidMount、componentDidUpdate等)

局限性

在了解上述知识后,我们可以很清晰的感知到 stack reconciler 的局限性:

  • 无法中断长任务:递归遍历一旦开始就无法中断,这会占用 js 主线程,导致无法响应其他高优先级任务
  • 缺乏任务优先级调度:所有的更新任务同步执行,享有同等优先级
  • 内存泄漏风险:当组件树十分庞大时,深度递归可能会引起栈内存泄漏

大型应用中的掉帧问题和用户体验缺陷

“帧”是什么?

在计算机图形学与交互式应用中,帧是指屏幕画面的一次完整更新,它是衡量画面流畅度的核心单位,帧率(FPS)即是指每秒内渲染的帧数,人眼视觉对 60FPS 以上的变化感知有限,但低于 60FPS 时则会感知到延迟、卡顿。主流浏览器的帧率大多为 60FPS ,即每帧 16.67ms。

什么是掉帧,掉帧是怎么引起的?

掉帧即是指帧率未达预期,导致动画或交互卡顿。以浏览器举例,预期帧率 60FPS,则每一帧(16.67ms)的生命周期内,浏览器需要完成 js 执行、样式计算(style)、布局(layout)、绘制(paint)、合成(composite)等步骤,否则就会导致掉帧。

1.2 浏览器渲染机制与主线程阻塞

渲染机制

浏览器渲染过程是指浏览器将 HTML、CSS、JavaScript 等代码转换为用户可视界面的过程。浏览器通过多线程协作提高渲染效率,其中关键线程的分工如下:

线程 职责 示例场景
主线程(Main Thread) 运行 JavaScript、DOM/CSS 解析、样式计算、布局、绘制、生成绘制指令 setTimeout、React 渲染、事件处理
合成器线程(Compositor Thread) 接收绘制指令、图层分块光栅化、加速图层合成、提交 GPU 显示 滚动、动画(如 transform 动画)
光栅化线程(Raster Thread) 将绘制指令转换为位图 处理图片解码、图层分块光栅化

正如上一章节所讲,浏览器渲染过程涉及多个阶段,且与主线程(Main Thread)紧密相关:

  1. 解析阶段:解析 HTML (遇到<script>标签时会阻塞解析)和 CSS,生成 DOM 和 CSSOM
  2. 样式计算阶段:将 DOM 和 CSSOM 合并,生成渲染树,树仅包含可见节点,并计算节点的最终样式
  3. 布局阶段:根据渲染树计算每个节点的精确位置和大小
  4. 绘制阶段:生成绘制指令,将布局结果转换为屏幕上的像素,输出绘制列表,记录绘制顺序
  5. 合成阶段:将页面分为多个图层并启用 GPU 加速合成
  6. 显示阶段:将合成后的位图通过显卡驱动传递给屏幕显示

浏览器的一次渲染过程即对应一帧的生成,单次渲染过程耗时过长即会导致帧率下降,即所谓的掉帧。结合浏览器关键线程分工职责和渲染过程的知识,可以知道,导致单次渲染耗时过长的原因有很多,与前端开发者息息相关的就是主线程阻塞。为了分析引起主线程阻塞的原因,我们还需要额外学习部分知识。

事件循环

JavaScript 的事件循环是什么?

JavaScript 的事件循环是其异步编程的核心机制,它决定了代码的执行顺序,使得单线程的 JavaScript 可以处理非阻塞任务

核心组成

事件循环的核心组成是调用栈任务队列

  • 调用栈:按顺序执行同步代码,后进先出(LIFO),执行一个函数时,将其压入栈顶,函数返回后弹出。如果栈溢出(比如深度递归)会抛出异常。
  • 任务队列:存储待执行的异步回调,分为宏任务队列、微任务队列、其他队列(比如requestAnimationFrame、web workers)。

调用栈与任务队列的协作模型的核心规则:

  • 同步代码属于当前宏任务,直接由调用栈执行
  • 异步任务分为宏任务、微任务两类,每次事件循环只执行一个宏任务,微任务必须在当前宏任务结束后立即执行,且必须清空队列

工作流程/执行顺序

  1. 执行当前调用栈中的同步代码(属于当前宏任务)
  2. 执行所有微任务,直到微任务队列为空
  3. 必要时渲染页面(浏览器决定)
  4. 从宏任务队列中取出一个任务执行(回到步骤 1)

核心规则

同步代码 > 微任务 > 渲染 > 宏任务

布局抖动(Layout Thrashing)

什么是布局抖动?

布局是指浏览器计算渲染树中每个节点的几何信息(精确位置、大小)的过程,发生在样式计算之后、绘制之前。修改影响几何属性的 CSS(如 width、margin)或读取布局属性(如 offsetWidth)都会触发布局重排。

布局抖动是指浏览器因频繁的强制同步布局造成的性能问题,表现为多次不必要的布局计算(重排/Reflow),影响页面渲染速度。其本质是代码中混合连续读写布局属性,迫使浏览器多次重新计算布局

常见触发场景

  • 循环中读写布局属性
  • 频繁访问布局 API:offsetTop、offsetLeft、scrollTop、getComputedStyle() 等
  • 动画中混合读写

长任务(Long Task)

什么是长任务?

长任务是指主线程上连续执行时间超过 50ms 的 JavaScript 代码或渲染操作,它会阻塞用户交互和页面渲染,导致明显的卡顿

长任务的标准

  • 时间阈值:50ms,是由 Google 根据人类感知延迟提出的临界值
  • 检测工具:Chrome DevTools 的 Performance 面板中,长任务会被标记为红色区块并显示 Long Task 警告

常见的长任务场景

  • 复杂的 JavaScript 计算
  • 未优化的 dom 操作
  • 同步网络请求
  • 加载未优化的三方脚本

长任务优化手段

  • 任务拆分
  • web workers 多线程:将计算密集型任务转到worker
  • 异步编程优化:通过 promise、async/await 等手段避免阻塞
  • 虚拟列表优化渲染:仅渲染可视区域内容
  • 惰性加载:按需加载非关键脚本

主线程阻塞原因分析

现在,我们可以知道,引起主线程阻塞的主要原因有以下几点:

  • 布局抖动
  • 长任务
  • 过多的微任务

1.3 现代前端应用的需求演进

动画/手势的高优先级更新

在现代前端应用中,动画/手势的高优先级更新是提升用户体验(UX)和界面流畅性的关键设计,其意义有以下几个方面:

  • 确保交互即时响应
  • 避免掉帧现象的发生
  • 提升手势操作的跟手性
  • 支持复杂的 UI 设计:拖拽、捏合缩放、页面过渡动画等复杂交互依赖高优先级更新
  • 与浏览器渲染管线的协同优化:高优先级动画(如 transform)可由合成器线程直接处理,无需主线程参与,从而避免布局/重绘

通过优先级调度策略,可以提升用户留存率和满意度。

异步数据加载与Suspense的诉求

传统前端应用中,通常会遇到以下几大问题:

  • “白屏”等待:传统应用在数据加载完全前,页面通常显示空白或 loading 图标,用户无法感知进度。
  • 不必要的加载状态:传统模式下,各个组件独立维护 loading 状态,会导致多次闪烁的 loading 提示
  • 竞态条件问题:典型场景是快速切换标签页时,前一次请求可能覆盖后一次请求的结果,比如从详情 A 跳到详情 B

而现代前端应用中,这些问题是严重影响用户体验的,针对以上问题,衍生出了以下现代需求:

  • 骨架屏:在数据加载时显示占位 UI,提升感知速度
  • 流式渲染:逐步发送 HTML 片段,让用户尽早看到内容
  • 状态统一:统一管理数据状态,仅在所有数据就绪后一次性渲染
  • AbortController:可中断正在进行的异步操作,避免资源浪费和竞态条件

这就要求应用在数据管理方案上能支持异步数据管理,因此 React 团队提出了Suspense 声明式异步数据管理方案。

为了解决 React15 及之前版本的渲染问题,应对日益激增的现代前端应用需求,React Fiber 架构于 16.x 版本诞生,并于后续版本中逐步优化完善。

第二章:Fiber架构的核心设计思想

2.1 Fiber 的三大角色定义

作为数据结构的Fiber节点(链表节点)

每个 Fiber 节点是一个 JavaScript 对象,React 源码中对 Fiber 节点的定义如下:

function FiberNode(
  this: $FlowFixMe,
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;

  // Fiber 链表指针
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;
  this.refCleanup = null;

  // 工作进度
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  // Effects 副作用标记
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  // 优先级调度
  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  // 双缓存树指针
  this.alternate = null;

  if (enableProfilerTimer) {
    // Note: The following is done to avoid a v8 performance cliff.
    //
    // Initializing the fields below to smis and later updating them with
    // double values will cause Fibers to end up having separate shapes.
    // This behavior/bug has something to do with Object.preventExtension().
    // Fortunately this only impacts DEV builds.
    // Unfortunately it makes React unusably slow for some applications.
    // To work around this, initialize the fields below with doubles.
    //
    // Learn more about this here:
    // https://github.com/facebook/react/issues/14365
    // https://bugs.chromium.org/p/v8/issues/detail?id=8538
    this.actualDuration = Number.NaN;
    this.actualStartTime = Number.NaN;
    this.selfBaseDuration = Number.NaN;
    this.treeBaseDuration = Number.NaN;

    // It's okay to replace the initial doubles with smis after initialization.
    // This won't trigger the performance cliff mentioned above,
    // and it simplifies other profiler code (including DevTools).
    this.actualDuration = 0;
    this.actualStartTime = -1;
    this.selfBaseDuration = 0;
    this.treeBaseDuration = 0;
  }

  if (__DEV__) {
    // This isn't directly used but is handy for debugging internals:

    this._debugSource = null;
    this._debugOwner = null;
    this._debugNeedsRemount = false;
    this._debugHookTypes = null;
    if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
      Object.preventExtensions(this);
    }
  }
}

作为执行单元的任务分片(Unit of Work)

在第一章节中,我们了解了长任务的相关知识,知道长任务会阻塞主线程,引起掉帧问题。为了解决这个问题,我们需要将长任务进行拆分,并在浏览器的每一帧中限制主线程执行任务的时间(React 默认初始时间是 5ms),这种将长任务拆成多段微小任务的技术被称为任务分片,分配到每一帧中去执行的操作被称为时间切片,二者协同实现 React 的高效渲染。

实现任务分片的关键是将同步更新变为可中断的异步更新:

  1. 构建链表树:通过 Fiber 节点的链表结构构建链表树,替代递归调用栈
  2. 分片执行:React 通过循环逐个处理 Fiber 节点,每处理完一个节点后检查剩余时间,决定继续还是暂停
  3. 中断和恢复:当有高优先级任务(如用户点击)或当前分片时间用尽时暂停任务,通过保存当前处理的 Fiber 节点指针,下次从断点继续

作为调度单位的优先级载体(Lane模型)

React Fiber 架构中实现任务优先级调度的核心思想是:将优先级信息分散在 Fiber 树的各个节点和更新信息中,通过动态计算(lanes)和调度器(scheduler)的协作实现优先级调度。

Lane(车道)的定义

  • 每个 Lane 是一个32位的二进制位
  • 每个二进制位代表一种优先级类型
  • 数字越小(位越低)优先级越高
  • Lane 互斥

Lane 的类型

React 18 版本中定义的类型有以下几种:

// 优先级从高到低
export const SyncLane: Lane = 0b0000000000000000000000000000001; // 同步任务(最高)
export const InputContinuousLane: Lane = 0b0000000000000000000000000000100; // 用户输入
export const DefaultLane: Lane = 0b0000000000000000000000000100000; // 默认更新
export const TransitionLane: Lane = 0b0000000000000000001000000000000; // 过渡更新,比如useTransition
export const IdleLane: Lane = 0b0100000000000000000000000000000; // 空闲任务(最低)

Lane 集合(Lanes)

多个 Lane 可以组合:lanes = InputLane | DefaultLane,表示一组需要处理的优先级集合

Lane 模型的工作原理

  1. 优先级分配:更新阶段根据交互类型分配 Lane
  2. 优先级收集:每个 Fiber 节点对象都有 lanes 属性和 childLanes 属性,lanes 属性保存该节点需要处理的优先级合集,childLanes 属性保存子节点中所有未处理的优先级合集
  3. 优先级调度:Fiber 树根节点合并所有子节点的 lanes 和 childLanes -> 从合并后的优先级合集中选取最高优先级的 Lane 进行处理 -> 只处理与选定 Lane 匹配的更新

这里先只做简单的概念了解,详细知识将在 4.1 章节中进行讲解。

2.2 关键设计目标

我们已经知道在 React15 及之前版本中,渲染一旦开始便无法中断,并且所有任务同步进行,享有同等优先级,会导致交互延迟。Fiber 架构为了解决这些问题的核心设计目标有三个

可中断、可恢复的渲染流程

关键设计

要实现可中断、可恢复的渲染流程,关键设计有三点:

  • 链表结构替代递归结构:Fiber 通过 child、sibling、return构成树形链表
  • 全局指针跟踪进度:nextUnitOfWork记录当前处理节点
  • 双缓存机制:current treework-in-progress tree 两棵树交替更新

完整流程

graph TD
    A[触发更新] --> B{有高优先级任务}
    B -->|是| C[中断当前任务]
    B -->|否| D[开始Render阶段]
    D --> E[处理Fiber节点]
    E --> F{时间用完}
    F -->|是| C
    F -->|否| G{还有子节点}
    G -->|是| H[移动到子节点]
    G -->|否| I{还有兄弟节点}
    I -->|是| J[移动到兄弟节点]
    I -->|否| K[回溯到父节点]
    K --> L{根节点}
    L -->|否| E
    L -->|是| M[完成Render阶段]
    M --> N[Commit阶段]
    N --> O[更新DOM]
    C --> P[保存进度]
    P --> Q[执行高优先级任务]
    Q --> R[恢复低优先级任务]

实现原理

  1. 渲染阶段可中断,核心代码如下:
    // 主工作循环
    
    /** @noinline */
    function workLoopConcurrentByScheduler() {
      // Perform work until Scheduler asks us to yield
      while (workInProgress !== null && !shouldYield()) {
        // $FlowFixMe[incompatible-call] flow doesn't know that shouldYield() is side-effect free
        performUnitOfWork(workInProgress);
      }
    }
    
    // 检查是否需要中断
    function shouldYield(): boolean {
      if (!enableAlwaysYieldScheduler && enableRequestPaint && needsPaint) {
        // Yield now.
        return true;
      }
      const timeElapsed = getCurrentTime() - startTime;
      if (timeElapsed < frameInterval) {
        // The main thread has only been blocked for a really short amount of time;
        // smaller than a single frame. Don't yield yet.
        return false;
      }
      // Yield now.
      return true;
    }
  2. 进度保存和恢复:中断时保留 nextUnitOfWork 指针指向未完成的 Fiber 节点,恢复时从上次中断的节点继续遍历,采用深度优先遍历策略,优先处理子节点(child),无子节点时处理兄弟节点(sibling),无兄弟节点时回溯父节点(return
  3. 双缓存保证一致性:current tree 展示当前界面,中断时保持不变,work-in-progress tree 构建新的 Fiber 树,构建完成后一次性替换 current tree 变成新的 current tree

React Fiber 架构通过可中断、可恢复的渲染流程拥有类似于操作系统的“多任务处理”能力,最终实现渲染不阻塞交互的用户体验

时间切片(Time Slicing)与增量渲染

在 React Fiber 中,时间切片和增量渲染是两个紧密相关的核心概念,共同解决了渲染过程中主线程阻塞的问题,但他们的关注点不同

时间切片 - 时间维度的解决方案

时间切片是将连续长任务切割成多个微小任务,并分散在浏览器的多个渲染帧中去执行的技术

它的核心目标有 2 个:

  • 防止 JavaScript 代码执行时间超过 50ms
  • 保证浏览器的每一帧中有足够的时间去执行渲染、动画和交互

其核心实现原理是通过 requestIdleCallback(React17及以下) 或 MessageChannel(React18) 来检测剩余时间,当时间用尽时中断任务并保存状态,下次空余时间到来时从中断点恢复

增量渲染 - 任务维度的解决方案

增量渲染是将整个渲染过程分解为多个可独立执行的子任务,并按优先级逐步完成的技术

它的核心目标是:

  • 将大型渲染任务分解为可独立执行的小任务
  • 允许先呈现部分内容,再逐步补充剩余内容

其实现原理是通过任务分片将组件树拆分为 Fiber 节点链表,通过 Lane 模型区分任务优先级,完成的部分先提交呈现,并结合双缓存机制保证渲染过程不影响当前显示:

graph LR
A[开始渲染] --> B[处理根节点]
B --> C[处理子节点1]
C --> D[处理子节点1.1]
D --> E[提交已完成部分]
E --> F[处理兄弟节点1.2]
F --> G{时间用尽}
G -->|是| H[保存状态并暂停]
G -->|否| I[继续处理]

时间切片和增量渲染协同工作流程如下:

  1. 增量渲染拆解任务:将整个渲染任务拆分为 Fiber 节点任务队列
  2. 时间切片分配时间:将浏览器每个渲染帧的空闲时间分配给任务队列
  3. 优先级调度介入:允许高优先级任务抢占当前执行
  4. 渐进式提交:完成的部分内容可先提交显示

基于优先级的任务调度

React 使用 Lane 模型定义优先级,每个优先级对应一个二进制位

调度流程

  • 任务标记阶段:当触发更新时,React 根据场景分配优先级
    function requestUpdateLane() {
      if (isTransition) return TransitionLane;       // useTransition 更新
      if (isUserBlockingEvent) return InputLane;    // 用户交互事件
      return DefaultLane;                          // 默认更新
    }
    
    // 示例:点击事件触发的更新
    button.addEventListener('click', (event) => {
      const updateLane = getEventPriority(event);   // 返回 InputContinuousLane
      scheduleUpdate(fiber, updateLane);
    });
  • 任务调度阶段:根据优先级和剩余时间控制执行顺序
      sequenceDiagram
          participant Browser as 浏览器
          participant Scheduler as React调度器
          participant Renderer as 渲染器
          
          Browser->>Scheduler: 新帧开始(16.6ms)
          Scheduler->>Renderer: 获取待处理任务
          Renderer->>Renderer: 按优先级排序任务
          loop 时间切片执行
              Renderer->>Renderer: 执行最高优先级任务
              Renderer-->>Scheduler: 检查剩余时间
              Scheduler->>Browser: 时间用尽则归还控制
          end
          Browser->>Browser: 执行绘制/用户输入
          Browser->>Scheduler: 下一帧继续
  • 中断与抢占机制:
    • 高优先级任务可直接中断正在执行的低优先级任务
    • 被中断的任务可保存进度到 nextUnitOfWork 指针中,后续恢复
  • 饥饿问题处理:长时间未执行的低优先级任务会被逐步提升优先级

关键调度策略

  1. 批量更新合并:比如相同优先级的多个 setState 会被合并
  2. 优先级反转预防:
    function ensureRootIsScheduled(root) {
      const nextLanes = getNextLanes(root, root.pendingLanes);
      const highestPriorityLane = getHighestPriorityLane(nextLanes);
      
      // 当前执行中的低优先级任务被高优先级打断
      if (existingCallbackPriority !== highestPriorityLane) {
        cancelCallback(existingCallbackNode); // 取消旧任务
        scheduleNewCallback(highestPriorityLane); // 调度新任务
      }
    }
  3. 时间切片分配:不同优先级的任务获得的时间片不同
    优先级 单次分配时间 是否可中断
    SyncLane 不限
    InputContinuousLane 5ms 是(仅限更高优先级)
    DefaultLane 2ms
    TransitionLane 1ms

第三章:Fiber的数据结构与算法实现

3.1 Fiber节点的详细结构解析

child、sibling、return指针的链表关系

Fiber 架构的设计哲学在于将树形结构转化为单向链表+树形回溯的混合结构,在保持父子关系的同时获得链表的高效遍历能力。实现该混合结构的关键点就是 child、sibling、return 这三个指针。

三个指针的作用与关系

指针名 指向 功能描述 类比传统树结构
child 第一个子节点 向下遍历的入口 node.firstChild
sibling 下一个兄弟节点 横向遍历同级节点 node.nextSibling
return 父节点 完成当前分支后向上回溯 node.parentNode

链表结构图解

假设有如下组件树:

<App>
  <Header />
  <Content>
    <Sidebar />
    <Main />
  </Content>
</App>

那该组件树对应的 Fiber 链表结构如下:

graph TD
    A[App] -->|child| B[Header]
    B -->|sibling| C[Content]
    C -->|child| D[Sidebar]
    D -->|sibling| E[Main]
    E -->|return| C
    D -->|return| C
    C -->|return| A
    B -->|return| A

指针关系表如下:

Fiber 节点 child sibling return
App Header null null
Header null Content App
Content Sidebar null App
Sidebar null Main Content
Main null null Content

alternate与双缓存树(Current/WorkInProgress)

alternate 指针和双缓存树是实现并发渲染和状态一致性的核心机制

双缓存树的本质

基本概念

树类型 作用 特点
current 当前界面正在显示内容对应的 Fiber 树 用户正在交互的稳定版本
workInProgress 正在内存中构建的 Fiber 树(即将成为下一针显示内容) 可中断、可丢弃的中间状态

alternate 指针的作用:每个 Fiber 节点都有一个 alternate 字段,指向另一棵树上的对应节点(current fiberNodeA.alternate <-> workInProgress fiberNodeA.alternate)

双缓存树的工作流程

初始渲染阶段:

sequenceDiagram
    participant R as React
    participant D as DOM
    
    R->>R: 创建 WorkInProgress 树(初始为空)
    R->>R: 从 Root 开始构建 Fiber 节点
    R->>R: 每个新节点设置 alternate=null
    R->>D: 首次渲染完成后, WorkInProgress 树变为 Current 树

更新阶段:

sequenceDiagram
    participant C as Current 树
    participant W as WorkInProgress 树
    participant R as React 调度器
    
    R->>W: 从 Current 树的 Root 克隆节点
    C->>W: 通过 alternate 互相指向
    loop 渲染阶段
        R->>W: 增量构建/更新节点
        W->>C: 通过 alternate 对比差异
    end
    R->>C: 提交完成后交换两棵树

effectTag与副作用链表(Effect List)

在了解 effectTag 之前,我们先了解下什么是 side effects(副作用)。

在 React 中,副作用(Side Effects)是指那些在组件渲染过程中无法完成的操作,因为它们会影响到组件外部或渲染周期之外的事物。简单来说,任何与 React 的纯渲染逻辑无关的操作都属于副作用。

为什么需要管理副作用?

  • React 组件应该是纯函数(在相同输入下返回相同输出),但副作用打破了这种纯度。因此,React 提供了专门的机制来处理副作用,以确保它们不会干扰组件的渲染过程,同时避免内存泄漏和不一致。

副作用的执行时机:

  • React 将副作用延迟到浏览器完成绘制之后执行,以避免阻塞屏幕更新。因此,副作用函数在组件渲染完成后运行(相当于类组件的 componentDidMountcomponentDidUpdate)。

React 管理副作用的钩子:useEffectuseLayoutEffect或自定义 Hooks

副作用的本质特征

特征 说明 示例
非纯操作 违反纯函数原则(相同输入 ≠ 相同输出) 数据获取、DOM 手动操作
外部依赖 与 React 渲染流程外的系统交互 访问浏览器 API、网络请求
时序敏感性 执行时机影响结果 事件监听、定时器
资源管理 需要显式清理 取消订阅、移除事件监听

副作用分类机制

副作用类型 处理方式 对应 Hook
同步 DOM 副作用 布局阶段同步执行 useLayoutEffect
异步副作用 浏览器绘制后执行 useEffect
状态更新副作用 随渲染流程处理 useState/useReducer setter
回调副作用 事件处理中执行 事件处理函数

正确处理副作用的规则

  1. 副作用隔离原则:避免渲染中执行(不要在 render 函数中或函数式组件主题中直接操作副作用),使用 Hook 封装(比如 useEffect)
  2. 明确声明依赖项(useEffect dependencies)
  3. 副作用清理机制(useEffect return)

effectTag 的本质与作用

  • effectTag是副作用标记系统。
  • 二进制位掩码:每个 effectTag 是一个二进制数,表示需要执行的副作用类型
  • 高效内存管理:通过位运算组合多个标记(如 Placement | Update)
  • 精确追踪:标记哪些 Fiber 节点需要进行 DOM 操作或其他副作用

常见的 effectTag 值

标记 值(二进制) 对应操作
NoEffect 0b00000000 无副作用
Placement 0b00000010 插入新节点
Update 0b00000100 更新属性/样式
Deletion 0b00001000 删除节点
Snapshot 0b00010000 生命周期 getSnapshotBeforeUpdate
Passive 0b00100000 useEffect 的副作用
Callback 0b01000000 setState 的回调

副作用链表(effect list)是只包含有副作用的 Fiber 节点的链表结构,它的构建时机是在 completeWork 阶段串联有 effectTag 的节点。它的关键指针如下:

  • firstEffect:链表头节点
  • nextEffect:下一个待处理节点
  • lastEffect:链表尾节点

副作用链表的构建过程

function completeWork(fiber) {
  // 处理当前节点工作...
  
  // 构建 Effect List
  if (fiber.effectTag > NoEffect) {
    if (fiber.return !== null) {
      // 将当前节点添加到父节点的 Effect List
      if (fiber.return.firstEffect === null) {
        fiber.return.firstEffect = fiber;
      } else {
        fiber.return.lastEffect.nextEffect = fiber;
      }
      fiber.return.lastEffect = fiber;
    }
  }
}

effectTag/副作用链表的工作原理

  1. 标记阶段:在 beginWork 中标记需要执行的 DOM 操作
  2. 链表构建:在 completeWork 阶段串联具有 effectTag 的节点
  3. 提交阶段:按链表顺序执行 DOM 操作

3.2 深度优先遍历的迭代实现

递归遍历的问题与链表遍历的优势

在 React15 及之前版本的 stack reconciler 协调器采用的遍历策略是递归遍历

递归遍历的问题

  1. 无条件全量遍历:从根节点开始递归处理每一个节点,无论节点状态是否变化
  2. 不可中断:依赖于 JavaScript 栈调用,一旦开始就必须执行到底,中途无法中断或跳过子节点
  3. 性能缺陷:阻塞主线程、无优先级调度

链表遍历的优势

  1. 可中断/恢复:通过全局变量(nextUnitOfWork)保存进度,结合 requestIdleCallback 或 messageChannel 时间切片实现中断和恢复。
  2. 按需遍历:通过 lanes 和 childLanes 标记 Fiber 节点优先级,跳过无需更新的子树,时间复杂度从递归的 O(n) 优化到 O(m)
  3. 优先级调度:结合 Lane 模型动态调整遍历顺序,实现高优先级任务中断低优先级任务
  4. 内存安全:链表遍历在堆内存中进行,不受调用栈限制,没有递归栈帧累积,因此支持任意深度的组件树

performUnitOfWork源码解析(含配图)

我们首先了解下 Fiber 架构中的工作循环:

/** @noinline */
function workLoopConcurrentByScheduler() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    // $FlowFixMe[incompatible-call] flow doesn't know that shouldYield() is side-effect free
    performUnitOfWork(workInProgress);
  }
}

performUnitOfWork 是 Fiber 架构中工作循环(workLoop)的最小执行单元,源码位置见这里

function performUnitOfWork(unitOfWork: Fiber): void {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  const current = unitOfWork.alternate;

  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    if (__DEV__) {
      next = runWithFiberInDEV(
        unitOfWork,
        beginWork,
        current,
        unitOfWork,
        entangledRenderLanes,
      );
    } else {
      next = beginWork(current, unitOfWork, entangledRenderLanes);
    }
    stopProfilerTimerIfRunningAndRecordDuration(unitOfWork);
  } else {
    if (__DEV__) {
      next = runWithFiberInDEV(
        unitOfWork,
        beginWork,
        current,
        unitOfWork,
        entangledRenderLanes,
      );
    } else {
      next = beginWork(current, unitOfWork, entangledRenderLanes);
    }
  }

  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
}

其核心代码如下,主要流程分为两个阶段:

function performUnitOfWork(unitOfWork: Fiber): void {
  // 1. 开始工作阶段(递)
  const current = unitOfWork.alternate;
  let next;
  next = beginWork(current, unitOfWork, entangledRenderLanes);

  // 2. 完成工作阶段(归)
  if (next === null) {
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
}

beginWork - 递阶段:处理组件更新(props/state 计算、diff 等)

核心代码逻辑:

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes
): Fiber | null {
  // 检查是否需要跳过更新(优化手段)
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    
    if (oldProps === newProps && !hasLegacyContextChanged()) {
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );
      if (
        !hasScheduledUpdateOrContext &&
        // If this is the second pass of an error or suspense boundary, there
        // may not be work scheduled on `current`, so we check for this flag.
        (workInProgress.flags & DidCapture) === NoFlags
      ) {
        // No pending updates or context. Bail out now.
        didReceiveUpdate = false;
        return attemptEarlyBailoutIfNoScheduledUpdate(
          current,
          workInProgress,
          renderLanes,
        );
      }
    }
  }

  // 根据组件类型执行不同处理
  switch (workInProgress.tag) {
    case FunctionComponent:
      return updateFunctionComponent(current, workInProgress, ...);
    case ClassComponent:
      return updateClassComponent(current, workInProgress, ...);
    case HostComponent: // DOM 节点
      return updateHostComponent(current, workInProgress, ...);
    case ScopeComponent: {
      if (enableScopeAPI) {
        return updateScopeComponent(current, workInProgress, renderLanes);
      }
      break;
    }
    // ...其他类型处理
  }
}

function updateFunctionComponent(
  current: null | Fiber,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
) {
  if (current !== null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  // React DevTools reads this flag.
  workInProgress.flags |= PerformedWork;
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

核心操作:

  • 优先级过滤:通过 renderLanes 跳过低优先级任务
  • diff 算法:在 updateFunctionComponent -> reconcileChildren 中生成子 Fiber
  • 副作用标记:设置 effectTag

completeUnitOfWork - 归阶段:完成 DOM 准备、收集副作用

核心代码逻辑:

function completeUnitOfWork(unitOfWork: Fiber): void {
  // Attempt to complete the current unit of work, then move to the next
  // sibling. If there are no more siblings, return to the parent fiber.
  let completedWork: Fiber = unitOfWork;
  do {
    if ((completedWork.flags & Incomplete) !== NoFlags) {
      // This fiber did not complete, because one of its children did not
      // complete. Switch to unwinding the stack instead of completing it.
      //
      // The reason "unwind" and "complete" is interleaved is because when
      // something suspends, we continue rendering the siblings even though
      // they will be replaced by a fallback.
      const skipSiblings = workInProgressRootDidSkipSuspendedSiblings;
      unwindUnitOfWork(completedWork, skipSiblings);
      return;
    }

    // The current, flushed, state of this fiber is the alternate. Ideally
    // nothing should rely on this, but relying on it here means that we don't
    // need an additional field on the work in progress.
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;

    let next;
    startProfilerTimer(completedWork);
    if (__DEV__) {
      next = runWithFiberInDEV(
        completedWork,
        completeWork,
        current,
        completedWork,
        entangledRenderLanes,
      );
    } else {
      next = completeWork(current, completedWork, entangledRenderLanes);
    }
    if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
      // Update render duration assuming we didn't error.
      stopProfilerTimerIfRunningAndRecordIncompleteDuration(completedWork);
    }
    if (next !== null) {
      // Completing this fiber spawned new work. Work on that next.
      workInProgress = next;
      return;
    }

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      workInProgress = siblingFiber;
      return;
    }
    // Otherwise, return to the parent
    // $FlowFixMe[incompatible-type] we bail out when we get a null
    completedWork = returnFiber;
    // Update the next thing we're working on in case something throws.
    workInProgress = completedWork;
  } while (completedWork !== null);

  // We've reached the root.
  if (workInProgressRootExitStatus === RootInProgress) {
    workInProgressRootExitStatus = RootCompleted;
  }
}

核心操作:

  • DOM 准备:在 completeWork 中创建/更新 DOM
  • 副作用收集:通过 firstEffect/lastEffect构建线性副作用链表,供提交阶段批量处理
  • 回溯机制:通过 return 指针实现非递归遍历

遍历过程图解

还是以之前的组件树为例:

<App>
  <Header />
  <Content>
    <Sidebar />
    <Main />
  </Content>
</App>

其链表遍历顺序图:

graph LR
    A[App] --> B[Header]
    B --> C[Content]
    C --> D[Sidebar]
    D --> E[Main]
    E --> F[回溯Content]
    F --> G[回溯App]

具体步骤:

  1. performUnitOfWork(App) → beginWork(App) → 返回子节点 Header → workInProgress = Header
  2. workLoopSync → performUnitOfWork(Header) → beginWork(Header) → 无子节点,返回null
  3. completeUnitOfWork(Header) → workInProgress = Header.siblingFiber(即Content)
  4. performUnitOfWork(Content) → beginWork(Content) → 返回子节点 Sidebar → workInProgress = Sidebar
  5. workLoopSync → performUnitOfWork(Sidebar) → beginWork(Sidebar) → 无子节点,返回null
  6. completeUnitOfWork(Sidebar) → workInProgress = Sidebar.siblingFiber(即Main)
  7. performUnitOfWork(Main) → beginWork(Main) → 无子节点无兄弟节点,返回 null
  8. completeUnitOfWork(Main) → 无子节点,无兄弟节点 → return 回溯到 Content → 无兄弟节点 → completeUnitOfWork(Content) → return 回溯到 App → 遍历结束

performUnitOfWork 的完整工作流程

sequenceDiagram
    participant Scheduler
    participant performUnitOfWork
    participant beginWork
    participant completeUnitOfWork

    Scheduler->>performUnitOfWork: 分配任务(Fiber节点)
    performUnitOfWork->>beginWork: 处理当前节点
    alt 有子节点
        beginWork-->>performUnitOfWork: 返回子节点
    else 无子节点
        beginWork-->>performUnitOfWork: null
        performUnitOfWork->>completeUnitOfWork: 进入完成阶段
        completeUnitOfWork-->>performUnitOfWork: 返回兄弟/父节点
    end
    performUnitOfWork-->>Scheduler: 返回下一个节点

3.3 Diff算法的Fiber化改造

同级比较(Key优化)在Fiber中的实现

React 列表元素为什么要加上 key 属性?

无论是传统 Diff 还是 Fiber Diff,key 的核心作用都是唯一标识同级元素。传统虚拟 DOM Diff 在 key 的利用上有两个瓶颈:

双指针遍历算法(O(n²) 复杂度)

传统 Diff 通过两层循环匹配新旧节点:

for (let i =0; i < newChildren.length; i++) {
  for (let j =0; j < oldChildren.length; j++) {
    if (newChild[i].key === oldChild[j].key) {
      // 匹配成功,复用节点
      break;
    }
  }
}

这带来的问题是:

  • 长度为 n 的列表最坏情况下需要 n² 次比较,性能随列表长度增大急剧下降
  • 仅能通过位置索引猜测节点移动,容易产生冗余 DOM 操作

复用粒度有限

  • 仅能复用虚拟 DOM 节点对象,仍需执行组件的生命周期和 DOM 属性对比
  • 无法跳过状态未发生改变的组件渲染(比如 shouldComponentUpdate 需手动优化)

Fiber 架构对比传统虚拟 DOM Diff 在 key 的利用上实现了质的飞跃。

在 Fiber 架构中,同级比较的核心逻辑集中在 reconcileChildren 函数中,它负责对比新旧子节点并生成新的 Fiber 树。beginWork 函数根据组件类型执行对应处理时触发该函数。

入口函数:reconcileChildren

根据当前是首次渲染还是更新调用 mountChildFibers 或 reconcileChildFibers

  • mountChildFibers:专用于组件首次渲染(mount 阶段)时创建子 Fiber 节点的方法,其核心特点是通过跳过副作用标记和 Diff 算法提升性能
  • reconcileChildFibers:更新阶段处理子节点协调的核心逻辑,根据子节点类型做不同处理,比如子节点为数组时调用 reconcileChildrenArray 函数处理。

核心优化逻辑

reconcileChildrenArray 采用多阶段遍历 + key 映射表的策略,将时间复杂度优化至O(n),主要优化包括:

  • 两轮遍历:先尝试从左到右顺序匹配,再处理移动/新增/删除
  • key 映射:剩余未匹配的旧节点存入map,实现O(1)查找
  • 节点复用:通过 key 和 type 精确匹配可复用的 Fiber 节点
  • 最小化 DOM 操作:仅标记需要移动和删除的节点

源码解析:以 reconcileChildrenArray 为例

阶段一:顺序遍历匹配(从左到右)

let oldFiber = currentFirstChild; // 旧 Fiber 链表头节点
let lastPlacedIndex = 0; // 记录最后一个无需移动的节点索引
let newIdx = 0;
let nextOldFiber = null;

// 第一轮遍历:顺序匹配 key 相同的节点
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
  if (oldFiber.index > newIdx) {
    nextOldFiber = oldFiber;
    oldFiber = null;
  } else {
    nextOldFiber = oldFiber.sibling;
  }

  // 尝试复用旧 Fiber(key & type 匹配)
  const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], lanes);

  if (newFiber === null) {
    // key 不匹配,跳出循环
    break;
  }

  // 标记是否需要移动
  lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
}

优化点:

  • updateSlot:检查 key 和 type,匹配则复用旧 Fiber,否则返回null
  • placeChild:比较旧 Fiber 的 index 和 newIdx,决定是否标记 placement(移动)

阶段二:处理剩余节点

  • 情况一:新节点已遍历完

    if (newIdx === newChildren.length) {
      // 删除剩余旧节点
      deleteRemainingChildren(returnFiber, oldFiber);
      return resultingFirstChild;
    }
    • 优化点:直接删除未匹配的旧节点,无需进一步比较
  • 情况二:旧节点已遍历完

    if (oldFiber === null) {
      // 创建剩余新节点
      for (; newIdx < newChildren.length; newIdx++) {
        const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
        // 链接到链表
      }
      return resultingFirstChild;
    }
    • 优化点:剩余新节点直接创建,无需匹配
  • 情况三:新旧节点均未遍历完

    // 构建旧节点 Map<key|index, Fiber>
    const existingChildren = mapRemainingChildren(oldFiber);
    
    // 遍历剩余新节点,尝试从 Map 中匹配
    for (; newIdx < newChildren.length; newIdx++) {
      const newFiber = updateFromMap(
        existingChildren,
        returnFiber,
        newIdx,
        newChildren[newIdx],
        lanes,
      );
    
      if (newFiber !== null) {
        // 标记移动或复用
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      }
    }
    
    // 删除未匹配的旧节点
    existingChildren.forEach(child => deleteChild(returnFiber, child));
    • 优化点:
      • mapRemainingChildren:将剩余旧节点存入 map,key 优先,无 key 时使用 index
      • updateFromMap:从 map 中查找可复用节点,减少遍历次数
      • placeChild:仅对需要移动的节点标记 placement,减少不必要的 DOM 操作

key的核心作用

  1. 精确匹配:key 是 React 中节点的唯一标识,应避免复用
  2. 高效复用:通过 key 直接定位旧节点,而非递归比较
  3. 移动优化:key 相同的节点即使位置发生改变,也能被正确标记移动,而非删除重建

性能对比(传统虚拟 DOM Diff 对比 Fiber Diff)

场景 传统 Diff Fiber Diff
时间复杂度 O(n²)(双指针循环) O(n) (map查找)
DOM 操作 可能多次移动/重建 仅进行必要更新(placement/deletion)
状态保留 仅复用 DOM 组件状态、hooks、ref全保留
列表渲染 性能随列表长度下降明显 万级列表仍然流畅

节点复用策略与bailout机制

复用条件:key 和 type 双匹配

源码入口:reconcileChildFibers → reconcileChildrenArray / updateSlot
核心代码逻辑(简化自ReactFiberChild.js):

function updateSlot(returnFiber: Fiber, oldFiber: Fiber | null, newChild: any, lanes: Lanes): Fiber | null {
  // Update the fiber if the keys match, otherwise return null.
  const key = oldFiber !== null ? oldFiber.key : null;
  if (typeof newChild === 'object' && newChild !== null) {
    if (newChild.key === key) {
      if (newChild.type === oldFiber?.type) {
        // 复用旧 Fiber(保留 state/DOM/hooks)
        const existing = useFiber(oldFiber, newChild.props);
        coerceRef(existing, element);
        existing.return = returnFiber;
        return existing;
      }
      // key 匹配但 type 不匹配,销毁旧节点
      deleteChild(returnFiber, oldFiber);
    } else {
      return null
    }
  }
  return null; // 不匹配则创建新节点
}

复用规则

  • key 和 type 都相同:调用 useFiber 克隆旧节点,保留:
    • 组件实例(class组件)和 hooks 链表(函数式组件)
    • DOM 引用(避免重建)
    • 状态和refs
  • key 相同,type 不同:销毁旧节点(标记deletion),创建新节点
  • key 不同:直接创建新节点

useFiber实现(状态保留核心)

function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
  // We currently set sibling to null and index to 0 here because it is easy
  // to forget to do before returning it. E.g. for the single child case.
  const clone = createWorkInProgress(fiber, pendingProps);
  clone.index = 0; // 重置索引
  clone.sibling = null; // 断开兄弟链接
  return clone;
}

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    // We use a double buffering pooling technique because we know that we'll
    // only ever need at most two versions of a tree. We pool the "other" unused
    // node that we're free to reuse. This is lazily created to avoid allocating
    // extra objects for things that are never updated. It also allow us to
    // reclaim the extra memory if needed.
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;

    // 省略部分源码

    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps;
    // Needed because Blocks store data on type.
    workInProgress.type = current.type;

    // We already have an alternate.
    // Reset the effect tag.
    workInProgress.flags = NoFlags;

    // The effects are no longer valid.
    workInProgress.subtreeFlags = NoFlags;
    workInProgress.deletions = null;

    if (enableProfilerTimer) {
      // We intentionally reset, rather than copy, actualDuration & actualStartTime.
      // This prevents time from endlessly accumulating in new commits.
      // This has the downside of resetting values for different priority renders,
      // But works for yielding (the common case) and should support resuming.
      workInProgress.actualDuration = -0;
      workInProgress.actualStartTime = -1.1;
    }
  }

  // Reset all effects except static ones.
  // Static effects are not specific to a render.
  workInProgress.flags = current.flags & StaticMask;
  workInProgress.childLanes = current.childLanes;
  workInProgress.lanes = current.lanes;

  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;

  // Clone the dependencies object. This is mutated during the render phase, so
  // it cannot be shared with the current fiber.
  const currentDependencies = current.dependencies;
  workInProgress.dependencies =
    currentDependencies === null
      ? null
      : __DEV__
        ? {
            lanes: currentDependencies.lanes,
            firstContext: currentDependencies.firstContext,
            _debugThenableState: currentDependencies._debugThenableState,
          }
        : {
            lanes: currentDependencies.lanes,
            firstContext: currentDependencies.firstContext,
          };

  // These will be overridden during the parent's reconciliation
  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;
  workInProgress.refCleanup = current.refCleanup;

  if (enableProfilerTimer) {
    workInProgress.selfBaseDuration = current.selfBaseDuration;
    workInProgress.treeBaseDuration = current.treeBaseDuration;
  }

  // 省略部分源码

  return workInProgress;
}

关键点:

  • 通过 alternate 指针复用旧 Fiber 对象(双缓冲技术)
  • 保留 stateNode(DOM/实例)和 memoizedState(状态)

bailout 机制(跳过子树协调)核心逻辑

源码入口:beginWork → updateFunctionComponent / updateClassComponent

核心代码逻辑(精简自ReactFiberBeginWork.js):

function updateFunctionComponent(
  current: null | Fiber,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
) {
  const oldProps = current.memoizedProps;
  const newProps = workInProgress.pendingProps;
  if (
    oldProps === newProps && // Props 浅比较
    !hasLegacyContextChanged() && // 旧 Context 未变
    workInProgress.type === current.type && // 组件类型相同
    !includesSomeLane(renderLanes, current.lanes) // 无高优先级更新
  ) {
    didReceiveUpdate = false
  }
  if (current !== null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  if (getIsHydrating() && hasId) {
    pushMaterializedTreeId(workInProgress);
  }

  // React DevTools reads this flag.
  workInProgress.flags |= PerformedWork;
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

bailout 条件

  • props 浅层相等(Object.is比较)
  • context 值未变化(新旧 Provider 值浅比较)。
  • 组件类型相同(无 type 变化)。
  • 无更高优先级更新(通过 lanes 模型判断)

bailoutOnAlreadyFinishedWork 实现

function bailoutOnAlreadyFinishedWork(current: Fiber, workInProgress: Fiber, renderLanes: Lanes): Fiber | null {
  cloneChildFibers(current, workInProgress); // 克隆子树
  return workInProgress.child;
}

关键操作:

  • 子树克隆:直接拷贝 current 树的子链表到 workInProgress 树。
  • 副作用继承:保留旧 Fiber 的 subtreeFlags(标记子树是否需要更新)。

性能优化对比

场景 传统 Diff Fiber Diff
节点复用 仅复用 DOM 状态+DOM+hooks
条件判断 无系统化 bailout 多维度检测(props/context/优先级)
子树跳过 不可能 直接克隆整棵子树(O(1) 操作)
时间复杂度 O(n²)(递归) O(n)(map查找+链表遍历)

第四章:Fiber的调度系统与并发模式

4.1 调度器(Scheduler)的实现原理

调度器(scheduler)是 Fiber 架构中并发模式的核心实现。调度器的核心目标有三点:

  • 任务可中断与恢复
  • 优先级调度
  • 时间切片

React17 及之前版本中任务中断与时间切片的核心逻辑在于浏览器 requestIdleCallback API 的利用,但该 API 有几个问题:

  • 不可靠的执行时机:requestIdleCallback 的执行依赖于浏览器的空闲时间,但不同浏览器的实现差异较大,且可能被扩展插件、防病毒软件等干扰
  • 无法保证任务顺序:requestIdleCallback 的回调执行顺序可能被打乱(尤其是设置了 timeout 时),而 React 需要精确控制任务优先级
  • 兼容性问题:部分浏览器(如旧版 Safari)不支持 requestIdleCallback,或实现不一致
  • 时间切片需求:React 需要将任务拆分为 5ms 左右的小块,而 requestIdleCallback 无法提供这种精细控制

React 18 改用 MessageChannel 模拟 requestIdleCallback 的行为,并在此基础上实现更高级的调度策略

MessageChannel与时间切片

MessageChannel 是浏览器提供的用于跨文档通信(跨窗口/iframe)的 API,也被广泛用于主线程的任务调度

基本用法

MessageChannel 创建一个双向通信通道,包含两个MessagePort:

  • port1:发送消息
  • port2:接收消息
    const channel = new MessageChannel();
    const { port1, port2 } = channel;
    
    // port2 监听消息
    port2.onmessage = (event) => {
      console.log("Received:", event.data);
    };
    
    // port1 发送消息
    port1.postMessage("Hello");

特点

  • 异步执行:postMessage 是宏任务,类似于 setTimeout(fn, 0),但比setTimeout更高效
  • 零延迟:浏览器会尽快执行回调,不受事件循环延迟影响
  • 跨线程通信:可用于 Web Worker、Service Worker 等场景(但 React 调度器仅用于主线程)

React scheduler 如何使用 MessageChannel

关键代码(Scheduler.js):

const performWorkUntilDeadline = () => {
  if (enableRequestPaint) {
    needsPaint = false;
  }
  if (isMessageLoopRunning) {
    const currentTime = getCurrentTime();
    // Keep track of the start time so we can measure how long the main thread
    // has been blocked.
    startTime = currentTime;

    // If a scheduler task throws, exit the current browser task so the
    // error can be observed.
    //
    // Intentionally not using a try-catch, since that makes some debugging
    // techniques harder. Instead, if `flushWork` errors, then `hasMoreWork` will
    // remain true, and we'll continue the work loop.
    let hasMoreWork = true;
    try {
      // workLoop 循环执行任务直到时间片用尽
      hasMoreWork = flushWork(currentTime);
    } finally {
      if (hasMoreWork) {
        // If there's more work, schedule the next message event at the end
        // of the preceding one.
        schedulePerformWorkUntilDeadline();
      } else {
        isMessageLoopRunning = false;
      }
    }
  }
};

function flushWork(initialTime: number) {
  // 省略非关键代码
  try {
    if (enableProfiling) {
      try {
        return workLoop(initialTime);
      } catch (error) {
        // 省略非关键代码
      }
    } else {
      // No catch in prod code path.
      return workLoop(initialTime);
    }
  } finally {
    // 省略非关键代码
  }
}

function workLoop(initialTime: number) {
  let currentTime = initialTime;
  advanceTimers(currentTime);
  currentTask = peek(taskQueue);
  while (currentTask !== null) {
    if (!enableAlwaysYieldScheduler) {
      if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
        // This currentTask hasn't expired, and we've reached the deadline.
        break;
      }
    }
    const callback = currentTask.callback;
    if (typeof callback === 'function') {
      // 省略
    } else {
      pop(taskQueue)
    }
    // 省略任务队列循环处理逻辑代码
    currentTask = peek(taskQueue);
  }
  // Return whether there's additional work
  if (currentTask !== null) {
    return true;
  } else {
    const firstTimer = peek(timerQueue);
    if (firstTimer !== null) {
      requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    }
    return false;
  }
}

function handleTimeout(currentTime: number) {
  isHostTimeoutScheduled = false;
  advanceTimers(currentTime);

  if (!isHostCallbackScheduled) {
    if (peek(taskQueue) !== null) {
      isHostCallbackScheduled = true;
      requestHostCallback();
    } else {
      const firstTimer = peek(timerQueue);
      if (firstTimer !== null) {
        requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
      }
    }
  }
}

function requestHostCallback() {
  if (!isMessageLoopRunning) {
    isMessageLoopRunning = true;
    schedulePerformWorkUntilDeadline();
  }
}

// DOM and Worker environments.
// We prefer MessageChannel because of the 4ms setTimeout clamping.
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline; // 接收消息后执行任务
schedulePerformWorkUntilDeadline = () => {
  port.postMessage(null); // 触发异步执行
};

调度流程如下:

  1. 任务入队:unstable_scheduleCallback 将任务加入最小堆
  2. 触发调度:requestHostCallback 通过 port.postMessage(null) 请求调度
  3. 执行任务:浏览器在下一事件循环中调用 port1.onmessage,执行 flushWork
  4. 时间切片:flushWork 每次执行最多 5ms,超时则暂停(shouldYieldToHost)

优先级标记(Lane模型)与任务队列

之前章节我们已经学习过优先级载体 Lane 模型的概念,其对应代码定义如下:

// SchedulerPriorities.js

export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;

// TODO: Use symbols?
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;

优先级与超时时间映射

var timeout;
switch (priorityLevel) {
  case ImmediatePriority:
    // Times out immediately
    timeout = -1; // 同步执行
    break;
  case UserBlockingPriority:
    // Eventually times out
    timeout = userBlockingPriorityTimeout; // 250ms 超时
    break;
  case IdlePriority:
    // Never times out
    timeout = maxSigned31BitInt; // 最大超时,var maxSigned31BitInt = 1073741823; 约12天
    break;
  case LowPriority:
    // Eventually times out
    timeout = lowPriorityTimeout; // 10s 超时
    break;
  case NormalPriority:
  default:
    // Eventually times out
    timeout = normalPriorityTimeout; // 5s 超时
    break;
}

任务队列(最小堆)

// SchedulerMinHeap.js
type Heap<T: Node> = Array<T>;
type Node = {
  id: number,
  sortIndex: number,
  ...
};

export function push<T: Node>(heap: Heap<T>, node: T): void {
  const index = heap.length;
  heap.push(node);
  siftUp(heap, node, index);
}

export function peek<T: Node>(heap: Heap<T>): T | null {
  return heap.length === 0 ? null : heap[0];
}

export function pop<T: Node>(heap: Heap<T>): T | null {
  if (heap.length === 0) {
    return null;
  }
  const first = heap[0];
  const last = heap.pop();
  if (last !== first) {
    // $FlowFixMe[incompatible-type]
    heap[0] = last;
    // $FlowFixMe[incompatible-call]
    siftDown(heap, last, 0);
  }
  return first;
}

任务调度入口:unstable_scheduleCallback

function unstable_scheduleCallback(
  priorityLevel: PriorityLevel,
  callback: Callback,
  options?: {delay: number},
): Task {
  var currentTime = getCurrentTime();

  var startTime;
  if (typeof options === 'object' && options !== null) {
    var delay = options.delay;
    if (typeof delay === 'number' && delay > 0) {
      startTime = currentTime + delay;
    } else {
      startTime = currentTime;
    }
  } else {
    startTime = currentTime;
  }

  var timeout;
  switch (priorityLevel) {
    case ImmediatePriority:
      // Times out immediately
      timeout = -1;
      break;
    case UserBlockingPriority:
      // Eventually times out
      timeout = userBlockingPriorityTimeout;
      break;
    case IdlePriority:
      // Never times out
      timeout = maxSigned31BitInt;
      break;
    case LowPriority:
      // Eventually times out
      timeout = lowPriorityTimeout;
      break;
    case NormalPriority:
    default:
      // Eventually times out
      timeout = normalPriorityTimeout;
      break;
  }

  // 计算任务过期时间
  var expirationTime = startTime + timeout;

  // 创建新任务对象
  var newTask: Task = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    sortIndex: -1,
  };
  if (enableProfiling) {
    newTask.isQueued = false;
  }

  if (startTime > currentTime) {
    // This is a delayed task.
    newTask.sortIndex = startTime;
    // 将任务插入最小堆(按expirationTime排序)
    push(timerQueue, newTask);
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      // All tasks are delayed, and this is the task with the earliest delay.
      if (isHostTimeoutScheduled) {
        // Cancel an existing timeout.
        cancelHostTimeout();
      } else {
        isHostTimeoutScheduled = true;
      }
      // Schedule a timeout.
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask);
    if (enableProfiling) {
      markTaskStart(newTask, currentTime);
      newTask.isQueued = true;
    }
    // Schedule a host callback, if needed. If we're already performing work,
    // wait until the next time we yield.
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback();
    }
  }

  return newTask;
}

unstable_scheduleCallback被调用路径

unstable_scheduleCallback 主要通过以下路径被调用:

  1. 状态更新:setState、useState、useReducer。
  2. 副作用调度:useEffect、useLayoutEffect。
  3. 并发模式 API:startTransition、useDeferredValue。
  4. 根节点渲染:ReactDOM.createRoot().render()。

核心调用链路

以 setState 为例,调用链如下:

setState -> enqueueSetState -> scheduleUpdateOnFiber -> ensureRootIsScheduled -> ensureScheduleIsScheduled -> scheduleImmediateRootScheduleTask -> unstable_scheduleCallback -> requestHostCallback -> schedulePerformWorkUntilDeadline -> port2.postMessage -> port1.onmessage -> performWorkUntilDeadline -> flushWork

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

class 组件在继承 React.Component 时通过 constructor 指定 instance.updater = classComponentUpdater,enqueueSetState 就是 classComponentUpdater 对象的属性方法

任务调度(unstable_scheduleCallback)到任务执行(performUnitOfWork)的关键在于 flushWork 中 currentTask.callback 的执行。currentTask.callback 是调用 unstable_scheduleCallback 时传入的第二个参数(unstable_scheduleCallback(ImmediateSchedulerPriority, processRootScheduleInImmediateTask)):

processRootScheduleInImmediateTask -> performWorkOnRoot -> renderRootConcurrent -> workLoopConcurrentByScheduler -> performUnitOfWork

此外,performWorkOnRoot 函数内在执行完 renderRootConcurrent 后会返回 exitStatus 值,用于判断完成状态,调用 finishConcurrentRender 后提交 commitRoot

4.2 并发模式(Concurrent Mode)的底层支持

高优先级更新的插队机制(如用户输入)

Fiber 高优先级任务的插队机制 是并发模式(Concurrent Mode)的核心特性之一,它允许高优先级任务(如用户交互)中断正在执行的低优先级任务(如数据加载)

以 setState 举例说明高优先级更新的插队机制

任务调度

当触发 setState 更新时:

  1. 创建 update 对象并标记 lane
  2. 调用 scheduleUpdateOnFiber 向上收集优先级到 root.pendingLanes
    enqueueSetState(inst: any, payload: any, callback) {
      const fiber = getInstance(inst);
      const lane = requestUpdateLane(fiber);
      
      const update = createUpdate(lane);
      update.payload = payload;
      if (callback !== undefined && callback !== null) {
        if (__DEV__) {
          warnOnInvalidCallback(callback);
        }
        update.callback = callback;
      }
    
      const root = enqueueUpdate(fiber, update, lane);
      if (root !== null) {
        startUpdateTimerByLane(lane, 'this.setState()');
        scheduleUpdateOnFiber(root, fiber, lane);
        entangleTransitions(root, fiber, lane);
      }
    
      if (enableSchedulingProfiler) {
        markStateUpdateScheduled(fiber, lane);
      }
    }
    
    // react-reconciler/src/ReactFiberWorkLoop.js
    function scheduleUpdateOnFiber(root, fiber, lane) {
      // Mark that the root has a pending update.
      markRootUpdated(root, lane); // 更新 root.pendingLanes
      ensureRootIsScheduled(root); // 触发调度
    }
    
    function ensureRootIsScheduled(root: FiberRoot): void {
      mightHavePendingSyncWork = true;
      ensureScheduleIsScheduled();
    }
    
    function ensureScheduleIsScheduled(): void {
      // 在当前事件结束时,遍历每个根,并确保以正确的优先级为每个根安排了一个任务
      // didScheduleMicrotask 用于防止调度冗余的 mircotask
      if (__DEV__ && ReactSharedInternals.actQueue !== null) {
        // We're inside an `act` scope.
        if (!didScheduleMicrotask_act) {
          didScheduleMicrotask_act = true;
          scheduleImmediateRootScheduleTask();
        }
      } else {
        if (!didScheduleMicrotask) {
          didScheduleMicrotask = true;
          scheduleImmediateRootScheduleTask();
        }
      }
    }
    
    function scheduleImmediateRootScheduleTask() {
      if (supportsMicrotasks) {
        scheduleMicrotask(() => {
          processRootScheduleInMicrotask();
        });
      } else {
        // If microtasks are not supported, use Scheduler.
        Scheduler_scheduleCallback(
          ImmediateSchedulerPriority,
          processRootScheduleInImmediateTask,
        );
      }
    }

中断低优先级任务(scheduleTaskForRootDuringMicrotask)

function processRootScheduleInMicrotask() {
  let prev = null;
  // 1. 遍历全局调度链表(firstScheduledRoot → lastScheduledRoot)
  let root = firstScheduledRoot;
  while (root !== null) {
    const next = root.next;
    const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
    if (nextLanes === NoLane) {
      root.next = null;
      if (prev === null) {
        // This is the new head of the list
        firstScheduledRoot = next;
      } else {
        prev.next = next;
      }
      if (next === null) {
        // This is the new tail of the list
        lastScheduledRoot = prev;
      }
    } else {
      // This root still has work. Keep it in the list.
      prev = root;
      if (
        syncTransitionLanes !== NoLanes ||
        includesSyncLane(nextLanes) ||
        (enableGestureTransition && isGestureRender(nextLanes))
      ) {
        mightHavePendingSyncWork = true;
      }
    }
    root = next;
  }
}

function scheduleTaskForRootDuringMicrotask(
  root: FiberRoot,
  currentTime: number,
): Lane {
  markStarvedLanesAsExpired(root, currentTime);
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
    rootHasPendingCommit);
  
  const existingCallbackNode = root.callbackNode;
  if (
    nextLanes === NoLanes ||
    (root === workInProgressRoot && isWorkLoopSuspendedOnData()) ||
    root.cancelPendingCommit !== null
  ) {
    // Fast path: There's nothing to work on.
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return NoLane;
  }
  
  if (
    includesSyncLane(nextLanes) &&
    // If we're prerendering, then we should use the concurrent work loop
    // even if the lanes are synchronous, so that prerendering never blocks
    // the main thread.
    !checkIfRootIsPrerendering(root, nextLanes)
  ) {
    // Synchronous work is always flushed at the end of the microtask, so we
    // don't need to schedule an additional task.
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackPriority = SyncLane;
    root.callbackNode = null;
    return SyncLane;
  } else {
    // We use the highest priority lane to represent the priority of the callback.
    const existingCallbackPriority = root.callbackPriority;
    const newCallbackPriority = getHighestPriorityLane(nextLanes);

    if (
      newCallbackPriority === existingCallbackPriority &&
      // Special case related to `act`. If the currently scheduled task is a
      // Scheduler task, rather than an `act` task, cancel it and re-schedule
      // on the `act` queue.
      !(
        __DEV__ &&
        ReactSharedInternals.actQueue !== null &&
        existingCallbackNode !== fakeActCallbackNode
      )
    ) {
      // The priority hasn't changed. We can reuse the existing task.
      return newCallbackPriority;
    } else {
      // Cancel the existing callback. We'll schedule a new one below.
      cancelCallback(existingCallbackNode);
    }

    const newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performWorkOnRootViaSchedulerTask.bind(null, root),
    );

    root.callbackPriority = newCallbackPriority;
    root.callbackNode = newCallbackNode;
    return newCallbackPriority;
  }
}

插队机制关键解析

(1)标记过期任务

markStarvedLanesAsExpired(root, currentTime);
  • 作用:检查 root.pendingLanes 中是否有低优先级任务因长期未执行而“饿死”(超过 timeout 未处理)
  • 插队机制:将过期的低优先级任务标记为 expiredLanes 使其升级为同步优先级(SyncLane)从而获得立即执行的机会

(2)计算下一个要处理的 lanes(getNextLanes)

const nextLanes = getNextLanes(
  root,
  root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  rootHasPendingCommit
);
  • 优先级比较:从 pendingLanes 中选择最高优先级的 lanes,规则包括:
    • 优先选择已过期的 expiredLanes(相当于强制插队)
    • 然后选择用户交互相关的 InputContinuousLane 或 DefaultLane
    • 避免与正在进行的渲染任务冲突

(3)中断低优先级

if (existingCallbackNode !== null) {
  cancelCallback(existingCallbackNode); // 取消当前正在执行的低优先级任务
}
  • 当检测到更高优先级的 newCallbackPriority 时,立即取消当前任务的回调执行。

(4)调度新任务

const newCallbackNode = scheduleCallback(
  schedulerPriorityLevel,
  performWorkOnRootViaSchedulerTask.bind(null, root)
);

Suspense的异步渲染流程

Suspense 的异步渲染流程主要围绕 异步数据加载懒加载组件(Code Splitting) 两大场景展开。Suspense 的工作原理是捕获子组件抛出的 Promise,并在 Promise 未完成时显示 fallback UI。

其异步状态管理的逻辑如下:

  • 当子组件(如 lazy 组件或使用 use Hook 的组件)需要等待异步操作(如数据加载或代码加载)时,它会 抛出一个 Promise。
  • Suspense 会捕获这个 Promise,并进入 挂起(Suspended)状态,显示 fallback UI。
  • 当 Promise 完成(resolve 或 reject),React 会重新尝试渲染子组件。

渲染流程详解

(1)Code Splitting(懒加载组件)

const LazyComponent = lazy(() => import("./Component"));
  • lazy 返回一个特殊对象(LazyComponent),包含 _statusPending/Resolved/Rejected)和 _result(加载的模块)。
  • 首次渲染时,readLazyComponentType 检查状态:
    • 若 Pending,抛出 thenable(Promise),触发 Suspense 显示 fallback。
    • 若 Resolved,返回模块并渲染

(2)数据获取(Async Data Fetching)
实验性 API unstable_createResource 允许在组件内同步读取异步数据:

const resource = unstable_createResource(fetchData);
function MyComponent() {
  const data = resource.read(id); // 可能抛出 Promise
  return <div>{data}</div>;
}
  • resource.read 检查缓存:
    • 若数据未加载,抛出 Promise,Suspense 捕获并显示 fallback。
    • 若数据已加载,直接返回6。
  • React 在微任务阶段重新尝试渲染完成加载的组件

4.3 实际案例:startTransition的工作原理

4.4 实际案例:setState 的完整更新流程

React 控制的实例化

组件定义阶段

// 用户编写的组件
class Demo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  render() {
    return <div>{this.state.count}</div>;
  }
}

React 内部实例化过程

当 React 需要创建组件实例时,不会直接调用 new Demo(),而是通过以下路径:

graph TD
  A[ReactDOM.render] --> B[创建根Fiber]
  B --> C[开始渲染]
  C --> D[beginWork阶段]
  D --> E[处理类组件]
  E --> F[constructClassInstance]
  F --> G[注入updater]

ReactDOM.render 是绑定在 ReactDOMRoot.prototype 上的原型方法,内部调用 react-reconciler/src/ReactFiberReconciler 中的 updateContainer 方法

import {updateContainer} from 'react-reconciler/src/ReactFiberReconciler';

function ReactDOMRoot(internalRoot: FiberRoot) {
  this._internalRoot = internalRoot;
}

ReactDOMRoot.prototype.render =
  // $FlowFixMe[missing-this-annot]
  function (children: ReactNodeList): void {
    const root = this._internalRoot;
    if (root === null) {
      throw new Error('Cannot update an unmounted root.');
    }

    // ……
    
    updateContainer(children, root, null, null);
  };

updateContainer 会调用 scheduleUpdateOnFiber 方法,从而一步一步按之前讲过的调用链路触发 performUnitOfWork 函数执行,并在 beginWork 中通过 updateClassComponent 调用 constructClassInstance、mountClassInstance等方法注入 updater

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  const current = container.current;
  const lane = requestUpdateLane(current);
  updateContainerImpl(
    current,
    lane,
    element,
    container,
    parentComponent,
    callback,
  );
  return lane;
}

function updateContainerImpl(
  rootFiber: Fiber,
  lane: Lane,
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): void {
  const update = createUpdate(lane);

  const root = enqueueUpdate(rootFiber, update, lane);
  if (root !== null) {
    startUpdateTimerByLane(lane, 'root.render()');
    scheduleUpdateOnFiber(root, rootFiber, lane);
    entangleTransitions(root, rootFiber, lane);
  }
}

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  switch (workInProgress.tag) {
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps = resolveClassComponentProps(
        Component,
        unresolvedProps,
        workInProgress.elementType === Component,
      );
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
  }
}

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
) {
  const instance = workInProgress.stateNode;
  let shouldUpdate;
  if (instance === null) {
    resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress);

    // In the initial pass we might need to construct the instance.
    constructClassInstance(workInProgress, Component, nextProps);
    mountClassInstance(workInProgress, Component, nextProps, renderLanes);
    shouldUpdate = true;
  }
}

setState 的执行逻辑

setState 是绑定在 React.Component.prototype 上的原型方法:

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
}

在 ReactDOM.render 初始化组件时,已经通过 constructClassInstance 实例化了 Component,并注入了 updater:

function constructClassInstance(
  workInProgress: Fiber,
  ctor: any,
  props: any,
): any {
  let instance = new ctor(props, context);
  instance.updater = classComponentUpdater;
}

const classComponentUpdater = {
  // $FlowFixMe[missing-local-annot]
  enqueueSetState(inst: any, payload: any, callback) {
    const fiber = getInstance(inst);
    const lane = requestUpdateLane(fiber);

    const update = createUpdate(lane);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback);
      }
      update.callback = callback;
    }

    const root = enqueueUpdate(fiber, update, lane);
    if (root !== null) {
      startUpdateTimerByLane(lane, 'this.setState()');
      scheduleUpdateOnFiber(root, fiber, lane);
      entangleTransitions(root, fiber, lane);
    }

    if (enableSchedulingProfiler) {
      markStateUpdateScheduled(fiber, lane);
    }
  },
  // ……
};

这里的 let instance = new ctor(props, context); 即是 new Component(props, context)

第五章:Fiber的渲染流程剖析

5.1 两阶段提交模型详解

Render阶段(可中断):Reconciliation与Effect收集

Commit阶段(不可中断):DOM更新与生命周期执行

5.2 双缓存树切换的完整流程

从WorkInProgress树到Current树的切换时机

错误边界与渲染恢复机制

第六章:Fiber的性能优化实践

6.1 减少协调开销的优化策略

React.memo、useMemo、useCallback的原理与误用

不可变数据与SCU(shouldComponentUpdate)优化

6.2 调试工具与性能分析

React DevTools的Fiber树 inspection

使用Profiler API定位渲染瓶颈

第七章:Fiber的延伸与生态系统影响

7.1 Fiber架构的通用性设计

自定义渲染器(如React Three Fiber)的实现原理

非DOM环境(React Native)的Fiber适配

7.2 其他框架的借鉴与对比

Vue 3的调度器设计 vs React Fiber

Svelte的编译时优化与运行时调度的取舍

第八章:从源码角度理解Fiber

8.1 关键源码文件导读

react-reconciler包的核心逻辑

beginWork、completeWork函数解析

8.2 手写迷你Fiber引擎

实现一个简化版Fiber调度器(300行代码Demo)