React Filber及核心原理

一. React Fiber 的核心目标‌

  • 增量渲染‌:将大型更新拆解为可中断的5ms任务块(时间切片),避免阻塞

示例:长列表渲染时,优先处理可视区域内容。

  • 主线程‌优先级调度‌:动态管理任务执行顺序(如用户交互 > 动画 > 数据加载)。

    // 优先级等级
    const PriorityLevels = {
    Immediate: 99, // 用户点击
    UserBlocking: 98, // 动画
    Normal: 97 // 数据加载
    };

  • 与浏览器协作‌:利用浏览器渲染周期(rAF)和空闲时间(rIC理念)优化性能。

1.高优先级任务用 requestAnimationFrame(对齐屏幕刷新率)

2.低优先级任务用 requestIdleCallback(利用空闲时间)

一句话定义:

React Fiber是React 16+的核心重写算法,通过增量渲染和任务分片实现高性能异步更新。旨在解决传统同步渲染阻塞主线程导致的卡顿问题。

二. React Fiber 的核心原理架构

2.1 数据结构变革

  • 递归同步 → 链表异步(Fiber节点)
  • 双缓冲机制(current/workInProgress树交替)

之前的版本中,React使用递归的方式处理组件树的更新,这个过程是同步的,一旦开始就不能中断,可能导致长时间占用主线程,造成界面卡顿,尤其是在处理复杂组件时。

Fiber是将组件树拆解为‌链表结构‌Fiber 节点(每个节点对应一个可中断/恢复的"工作单元"),通过‌优先级调度‌(如用户交互任务优先)和‌时间切片‌(利用浏览器空闲时间分批次处理任务),实现异步可中断的渲染流程。

Fiber 在协调阶段(Render Phase)增量构建虚拟树并标记副作用(如 DOM 更新),在提交阶段(Commit Phase)同步执行所有变更,同时通过‌双缓冲机制‌(交替使用 current 和 workInProgress 树)和‌副作用链表‌优化性能,从而支持并发模式(Concurrent Mode),显著提升复杂应用的响应速度与流畅度。

在 React 的 Fiber 架构中,requestAnimationFrame、‌链表‌ 和 ‌调度器‌三者紧密结合,主要体现在 ‌任务调度策略‌ 和 ‌执行效率优化‌

2.1. Fiber的双链表系统

Fiber 树本身的链表结构‌和‌副作用链表(effect list)‌是‌两个不同的链表

js 复制代码
class FiberNode {
  child: Fiber | null;      // 子节点
  sibling: Fiber | null;    // 兄弟节点
  return: Fiber | null;     // 父节点
  firstEffect: Fiber | null;// 副作用链表头
}
2.1.1. Fiber 树链表(主链表)‌

作用‌:

表示组件树完整结构,用于协调阶段(reconciliation)遍历任务分片

数据结构‌:

每个 Fiber 节点通过 child、sibling、return 三个指针构成‌树形链表‌(深度优先遍历的线性化结构)。

js 复制代码
class FiberNode {
  child: Fiber | null;    // 第一个子节点
  sibling: Fiber | null;  // 下一个兄弟节点
  return: Fiber | null;   // 父节点
  // ... 其他属性(type, stateNode 等)
}

特点‌:

1.用于递归的‌可中断恢复‌(通过保存当前处理的 Fiber 节点指针)。

2.是 React 处理组件更新的‌核心数据结构‌。

‌与调度器的关系‌

每个链表节点(FiberNode)包含任务信息优先级标记指针(child/sibling/return)

调度器通过链表实现‌可中断恢复‌:保存当前节点指针,下次从中断处继续

2.1.2. 副作用链表(effect list)‌

作用‌:

收集所有需要执行副作用的 Fiber 节点(如 DOM 操作、生命周期调用),在提交阶段(commit phase)批量处理

‌数据结构‌:

通过 firstEffect(链表头)、lastEffect(链表尾)、nextEffect(每个节点的指针) 指针构成的‌单向链表‌。

JS 复制代码
class FiberNode {
  firstEffect: Fiber | null; // 链表头
  lastEffect: Fiber | null;  // 链表尾
  nextEffect: Fiber | null;  // 下一个副作用节点
  effectTag: Placement | Update | Deletion; // 副作用类型
}

‌关键特性:

1‌.高效批处理‌

1.是 Fiber 树的‌子集‌,仅包含需要处理的节点。

2.在协调阶段动态构建,提交阶段直接遍历此链表批量处理 DOM 操作(如 DOM更新生命周期方法

2‌.优先级处理‌

1.调度器控制构建顺序高优先级任务优先生成链表 (如用户输入触发的更新可能打断正在进行的渲染,优先处理相关副作用,然后再处理低优先级的任务)

2.支持中断恢复,保留未完成的链表状态

3‌.执行分类和时机

‌同步副作用(useLayoutEffect)‌:

  • 执行时机:DOM更新后,浏览器绘制前

  • 特点:必须立即完成的工作,会阻塞浏览器渲染

  • 类比:就像手术中的紧急止血,必须马上处理

  • 典型场景:调整元素位置、获取DOM尺寸、必须同步执行的动画效果
    ‌异步副作用(useEffect)‌:

  • 执行时机:浏览器完成绘制后空闲时间

  • 特点:可以稍后完成的工作,不阻塞渲染

    • 类比:就像整理病历档案,可以等主要工作完成后处理
  • 典型场景:API请求、事件订阅、数据获取、事件监听、非关键的初始化操作

js 复制代码
function Example() {
  // 同步副作用(紧急)
  useLayoutEffect(() => {
    const rect = element.getBoundingClientRect();
    console.log('元素尺寸:', rect);
  });

  // 异步副作用(非紧急)
  useEffect(() => {
    fetch('/api/data').then(/*...*/);
  });
}

4‌.遍历顺序‌

采用深度优先后序遍历,确保:

  • 子节点副作用先于父节点处理
  • DOM操作符合依赖关系(如先删除子节点再更新父节点)
为什么分开设计?‌

1‌.关注点分离‌

Fiber 树链表负责‌任务调度和中断恢复‌(协调阶段)。

副作用链表负责‌高效执行 DOM 操作‌(提交阶段)。

‌2.性能优化‌:

提交阶段只需遍历少量副作用节点,避免全树遍历。
批量处理 DOM 更新,减少浏览器重排/重绘。

总结‌:

‌不是同一个链表‌,但副作用链表是 Fiber 树的衍生结构

Fiber 树是‌全局任务管理‌的骨架,副作用链表是‌局部优化‌的结果。

两者协作实现 React 的‌可中断渲染‌和‌高效更新‌

2.2. requestAnimationFrame(rAF)‌:调度时机控制‌

‌requestAnimationFrame(rAF)‌ 是浏览器提供的原生 API,用于在下一次屏幕刷新(通常是每秒 60 次,即 16.6ms/帧)前执行回调函数。

‌作用‌

在每一帧渲染前执行高优先级任务(如动画、UI 更新)

‌与调度器的关系‌:

1.动画优先级任务‌:动画、布局更新等高优先级任务,通过 rAF 对齐浏览器刷新率(通常 60fps/16.6ms)

js 复制代码
// 伪代码:调度器中的优先级处理
if (task.priority === UserBlockingPriority) {
  requestAnimationFrame(() => performTask(task));
}

‌2.避免布局抖动‌:在 rAF 回调中批量处理 DOM 读写,确保 DOM 操作在浏览器渲染周期内执行,减少强制布局计算(如 offsetWidth)。

特性 requestAnimationFrame requestIdleCallback
用途 处理高优先级任务(如动画、用户交互) 处理低优先级任务(如数据加载)
Fiber 中的应用场景 同步任务队列 异步任务队列(后备 polyfill)
触发时机 每帧开始前(16.7ms/60FPS) 浏览器空闲时(无高优先级任务)
用途 动画、视觉更新 后台任务(日志、预加载)
优先级 高(与渲染强相关) 低(可被高优先级任务打断)
React调度参考 影响高优先级任务分片 启发低优先级任务分片(如并发渲染)
执行耗时限制 需控制在3-4ms以内 默认50ms(通过timeout参数可调)
兼容性 IE10+ 需polyfill(如React的scheduler

2.3. React Fiber 调度机制(Scheduler)

是其并发模式(Concurrent Mode)的核心底层机制,相比传统更新调度方式具有以下显著优势:

2.3.1.优先级调度‌系统

‌ 更精细的任务优先级控制‌

  • 5级优先级划分‌机制:

协作流程示例‌

  • ‌用户点击按钮‌(Immediate 优先级):
    同步执行回调,更新状态,标记相关 Fiber 节点。
  • ‌触发动画‌or搜索建议(UserBlocking 优先级):
    将更新任务放入 rAF 队列,确保下一帧渲染前完成。
  • ‌数据加载‌(Low 优先级):
    拆分任务为 5ms 的块,在调度器检测到空闲时逐步执行。
js 复制代码
// 代码示例:优先级实战判断
startTransition(() => { 
  // ↓ Low优先级(数据加载)
  setData(fetchData()); 
});

button.onclick = () => {
  // ↑ Immediate优先级(用户交互)
  setState(...); 
};
  • 动态调整‌机制:高优先级任务可中断低优先级任务(如渲染中途响应用户点击)。
  • 中断/恢复机(保存当前Fiber指针)
2.3.2.执行控制机制
2.3.2.1. 时间切片(Time Slicing)机制

时间切片逻辑‌:将任务拆分5ms 的块,动态检查空闲时间,通过shouldYield()判断是否让出主线程,保证UI响应。优先处理用户输入高优先级事件,避免阻塞用户交互。

这听起来类似于浏览器的requestIdleCallback API,允许在空闲时间执行任务,但React实现了自己的调度器。

requestIdleCallback(rIC)的理念‌

空闲时段处理低优先级任务‌:rIC 在浏览器空闲时执行非紧急任务(如日志、预加载),避免干扰关键渲染和交互。

『时间分片』作为执行策略‌(设计原则)的核心概念

  • 是什么‌:是Fiber架构的核心设计思想,将渲染工作分解为小任务块
  • 为什么‌:保证浏览器不卡顿(主线程可响应事件)
  • 特征‌:不关心具体如何实现分片,策略决定需要切片
2.3.2.2. 空闲期利用‌:

通过requestIdleCallback的polyfill(兼容方案),在浏览器空闲时执行低优先级任务(如日志上报、预渲染)。

2.3.2.3 ‌任务饥饿保护‌:

防止低优先级任务因长期无法执行被"饿死"(超过超时时间会强制提升优先级)。

js 复制代码
// 伪代码实现
if (task.expirationTime < currentTime) {
  task.priority = Immediate; // 强制升级
}
2.3.3 调度机制实现方案
2.‌3.3.1. 基于MessageChannel的调度‌:React 自研调度器


浏览器兼容处理

‌1.降级策略‌:

  • 首选:MessageChannel(精度最高)
  • 次选:setImmediate(IE10+)
  • 兜底:setTimeout(所有浏览器)

2‌.关键原因‌:

  • MessageChannel的postMessage能实现微秒级调度
  • 比setTimeout更精准(不受浏览器最小延迟限制)
2.‌3.3.2. 性能优化策略(3大核心)

1‌.任务批处理‌:

  • 合并多个setState更新
    示例:连续3次setState只会触发1次渲染

‌2.优先级缓存‌:

  • 记住组件树的优先级标记
  • 跳过未变化子树的调度(bailout机制)

3‌.动态时间切片‌:

  • 基础值5ms,但会根据设备性能调整
  • 高性能设备可能用2ms,低端设备可能用10ms

为什么选择5ms分片?

  • 5ms是启发式值(heuristic),实际会根据设备性能动态调整
  • 人眼最小感知延迟约16ms(60fps)
  • 5ms分片确保至少剩余11ms处理用户输入
  • 平衡任务进度和响应速度的黄金值

『时间分片』作为实现方案‌(技术手段)的概念

  • 怎么做‌:通过MessageChannel/postMessage控制执行时长 ‌
  • 关键代码‌:实际测量任务执行时间的while循环
  • 特征‌:依赖具体API和算法实现的核心概念,实现确保能正确切片`
2.3.4. 对比传统调度方案‌
能力 传统setState更新 React调度器
任务中断 ✗ 不可中断 ✔ 高优任务可中断低优任务
浏览器阻塞 可能阻塞主线程 通过时间切片避免阻塞
优先级控制 无差别处理 5级动态优先级
后台任务利用 依赖手动实现 内置空闲期调度

2.4 双缓冲机制

2.4.1. 双缓冲的定义

Fiber 架构的核心机制‌,用于在 ‌协调阶段(Reconciliation)‌ 和 ‌提交阶段(Commit)‌ 之间高效管理组件状态更新,确保 React 的 ‌可中断渲染‌ 和 ‌一致性更新‌

React 在 Fiber 架构中维护 ‌两棵 Fiber 树‌:

  • current 树‌:当前已渲染的 Fiber 树(对应真实 DOM,对应屏幕上显示的内容)。
  • workInProgress 树‌:正在构建的新 Fiber 树(用于计算更新,尚未提交到屏幕)。
2.4.2. 双缓冲与调度的关系‌:
  • 调度器决定"何时执行"
  • 双缓冲解决"如何安全执行"
2.4.3. 双缓冲的核心流程‌

‌(1) 协调阶段(Reconciliation)‌

  • React 从 current 树克隆出 workInProgress 树(通过 alternate 指针关联)。
  • workInProgress 树上进行 ‌Diff 计算‌标记需要更新的节点(effectTag)。
  • 如果任务被中断(如高优先级任务插入),可以丢弃 workInProgress 树并重新开始,而不会影响 current 树(已渲染的 UI)。

‌(2) 提交阶段(Commit)‌

当 workInProgress 树构建完成,React 执行 ‌原子性切换‌:

  • 新的 workInProgress 树变为 current 树(对应最新 UI)。
  • 旧的 current 树变为新的 workInProgress 树(供下次更新使用)。

这段如果不是很好理解,D老师给了例子哈哈:

想象您有两块画板

  • 展示画板(current树)- 观众正在看的

  • 备用画板(workInProgress树)- 您正在修改的
    更新流程

  • 1.当需要修改时,您会复制展示画板到备用画板(克隆)

  • 2.在备用画板上进行修改(添加/删除元素)

  • 3.修改完成后瞬间交换两块画板的位置

  • 4.原来的展示画板变成新的备用画板(供下次修改使用)

用我的话形容就是:"两个东西来回倒腾,每次改完都把最新的甩到前台给人看,用完的旧货也不扔,拉回去重新刷漆等着下次用"

🔍 需要强调的细节:

  • 交换是"瞬间完成"的(像电灯开关,没有中间状态
  • 旧树会被复用
js 复制代码
// 伪代码
// 协调阶段:构建更新
function beginWork() {
  const workInProgressFiberTree = cloneFiberTree(currentFiberTree); 
  performUnitOfWork(workInProgressFiberTree); // 增量更新
  
  if (shouldYieldToBrowser()) {
    return; // 可中断
  }
  commitRoot(); // 完成更新
}

// 提交阶段:完成更新切换
function commitRoot() {
  currentFiberTree = workInProgressFiberTree;  // 新树变为当前树
  workInProgressFiberTree = null;             // 清空工作树
}

通过维护两棵 Fiber 树(currentworkInProgress),实现:

  • 计算与渲染分离。
  • 高优先级任务抢占。
  • 无闪烁的 UI 更新。

最终提升复杂应用的流畅度和响应速度。

双缓存机制 vs 调度系统

三、总结

同步渲染与react fiber的对比