前言

在Fiber出现之前,React的Reconciliation(协调)是“一条道走到黑”的递归过程。一旦开始更新组件树,就像启动了脱缰野马,必须一口气跑完,中间不能停。如果组件树很庞大,主线程被长期占用,用户点击、输入都没反应,页面卡成PPT。

React 16推出的Fiber架构,彻底改变了这一局面。它把渲染任务拆成一个个小任务(Fiber节点),可以中断、恢复、分优先级。就像流水线上的工人,干完一个零件可以喝口水,接个电话,再回来继续。今天我们就来看Fiber到底是怎么做到的。

一、Fiber是什么?一个“有状态”的虚拟DOM节点

以前的虚拟DOM节点就是普通JS对象,没有中断能力。Fiber节点增加了许多字段,用来记录“工作进度”。

// 简化的Fiber节点
const fiber = {
  type: 'div',           // 节点类型
  props: {},             // 属性
  return: parentFiber,   // 父节点
  child: childFiber,     // 第一个子节点
  sibling: siblingFiber, // 兄弟节点
  alternate: lastFiber,  // 上一次渲染的Fiber(用于对比)
  effectTag: 'UPDATE',   // 这个节点需要做什么操作(增删改)
  // 还有很多调度相关的字段
};

每个Fiber节点就是一个“工作单元”。React的任务就是遍历这棵树,处理每个单元。关键区别:这种遍历可以暂停

二、Fiber如何实现“中断”?——循环+requestIdleCallback

以前是递归调用,一旦开始就难以打断。Fiber改成了循环:从根节点开始,while循环里处理每个工作单元。但React不是一口气跑完,而是主动让出控制权

核心调度函数workLoop(简化版):

function workLoop(deadline) {
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    // 检查是否还有剩余时间,或者是否有更高优先级任务
    shouldYield = deadline.timeRemaining() < 1;
  }
  requestIdleCallback(workLoop); // 下次有空再继续
}
requestIdleCallback(workLoop);

requestIdleCallback会在浏览器空闲时调用workLoop,并传入一个deadline参数,告诉你还有多少剩余时间。当时间用尽或出现高优先级任务(比如用户点击),shouldYield变成true,退出循环,让主线程处理UI或交互。等下次空闲,再从中断的地方继续。

三、Fiber树的“双缓冲”:电流与复用

Fiber架构还有一个重要概念:current树workInProgress树

  • current树:当前屏幕上显示的内容对应的Fiber树。
  • workInProgress树:正在内存中构建的新Fiber树。

React在做更新时,克隆current树生成workInProgress树,在上面协调(比较差异、打effectTag)。等所有工作完成,再把workInProgress树一次性提交给DOM,然后让current指向它。这个过程叫“双缓冲”,避免了视觉闪烁,也保证了中断时屏幕还是旧的、稳定的内容。

四、Fiber的工作流程:两个阶段

  • Render阶段:可中断。遍历Fiber树,计算哪些节点需要更新,给Fiber打上effectTag(比如“插入”、“更新”、“删除”)。这个阶段的代码可以随时中断恢复,因为还没操作真DOM。
  • Commit阶段:不可中断。一次性将effectTag应用到真实DOM上,用户可见变化。这个阶段必须同步完成,不能中断。

所以你的所有生命周期钩子(除componentDidMount/componentDidUpdate)都在Render阶段执行,可能会被多次调用。这也是为什么React 16之后,componentWillMount等被标记为不安全的。

五、优先级调度:让用户交互优先

不同更新有不同的优先级。比如用户点击按钮的更新,优先级比网络请求回来的数据更新高。Fiber调度器会根据过期时间(expiration time)来决定先处理哪个。

  • 同步优先级:立即执行(如用户输入)。
  • 异步优先级:等空闲时执行(如数据预加载)。
  • 低优先级:可被打断,甚至被丢弃。

这样用户点按钮时,React可以立即响应,哪怕正在渲染大列表,也会先让给交互。

六、副作用(Effect)收集:像收快递一样

每个Fiber节点可以带有“副作用”(effectTag)。在Render阶段,React会把这些有副作用的节点收集成一个单向链表(effects list),这样Commit阶段只需遍历这个链表,而不用重新遍历整个树,效率很高。

// 简化的副作用链表
const nextEffect = firstEffect;
while (nextEffect) {
  switch (nextEffect.effectTag) {
    case 'PLACEMENT': commitPlacement(nextEffect); break;
    case 'UPDATE': commitUpdate(nextEffect); break;
    case 'DELETION': commitDeletion(nextEffect); break;
  }
  nextEffect = nextEffect.nextEffect;
}

七、总结:Fiber让React变成了“分时操作系统”

  • Fiber节点:带状态的工作单元,可以记录进度。
  • 调度器:用requestIdleCallback或MessageChannel实现时间切片,不让主线程卡死。
  • 双缓冲:当前树和工作中树,保证流畅切换。
  • 两个阶段:Render可中断,Commit不可中断。
  • 优先级:高优先级插队,低优先级让路。

正是因为Fiber架构,才让React能够实现并发模式(Concurrent Mode)、useTransition、Suspense等高级特性。它把React从“暴君”变成了“亲民总理”,懂得适时让出资源,让用户感觉“这个网页好丝滑”。

下次你写React,可以骄傲地说:我知道它为什么这么流畅,因为Fiber里藏着时间切片的黑魔法。

标签: none

添加新评论