🌿 Fiber 异步渲染机制 & 时间切片原理详解

源代码目录: react-source-code-learning

基于 requestIdleCallback 实现的简易 Fiber 架构

------ 一次搞懂:为什么要拆任务?为什么要让出时间?怎么暂停和恢复渲染?


🧩 一、问题出发:为什么要拆任务?

当我们的页面节点非常多(比如成千上万个 DOM),如果你一次性去执行所有渲染逻辑:

js 复制代码
root.appendChild(allDomNodes)

浏览器就会卡死几百毫秒甚至几秒,用户看到的就是:

"页面白屏一阵,然后一下子全部出来"。

这是因为浏览器主线程是单线程的 ,JS 执行会阻塞渲染

而我们希望浏览器能在执行 JS 的同时,保持流畅的渲染和响应用户操作

这就要求我们把"大任务"拆成一个个"小任务"去执行。


💡 二、解决思路:时间切片(Time Slicing)

"每次做一点点,然后让浏览器喘口气。"

浏览器提供了一个非常好用的 API:

scss 复制代码
requestIdleCallback(callback)

它会在浏览器空闲的时候执行回调函数。

并且回调会带一个参数:deadline,其中的 deadline.timeRemaining() 告诉我们------

👉 浏览器这一帧还有多少空闲时间(毫秒)。

于是我们可以做这样的逻辑:

  1. 我先拿一点任务出来做。
  2. 如果空闲时间还多(> 1ms),继续做。
  3. 如果时间不够了(< 1ms),先暂停,下一帧再继续。

这就像我们把一次性吃完的"大餐",分成了"几口几口慢慢吃"。


⚙️ 三、核心实现流程(主循环 workLoop)

🔁 工作循环思路

每一帧浏览器空闲时,requestIdleCallback 会帮我们调用 workLoop(deadline)

我们就可以在里面不断处理 fiber 节点(一个 fiber 节点代表一个 DOM 节点的任务):

js 复制代码
function workLoop(deadline) {
  let shouldYield = false; // 是否需要让出时间片
  while (!shouldYield && nextWorkOfUnit) {
    nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit);
    shouldYield = deadline.timeRemaining() < 1;
  }
  requestIdleCallback(workLoop);
}

🧠 为什么要有 shouldYield?

  • 为什么要让出时间?
    因为浏览器要渲染、动画、响应点击,不能被你霸占主线程。
  • 如果不让出时间?
    页面会掉帧甚至冻结,用户点什么都没反应。
  • deadline.timeRemaining() 的好处?
    浏览器告诉我们还能工作多久,我们自己不用猜。

🌳 四、Fiber 单元任务执行(performWorkOfUnit)

performWorkOfUnit 就是每个"小任务"的执行函数。

一个 fiber 节点对应一个虚拟 DOM 节点。

每次执行这个函数,我们:

  1. 创建真实 DOM 节点
  2. 更新属性
  3. 挂到正确的父节点上
  4. 找下一个要处理的节点(child / sibling / parent.sibling)

✨ 1. 创建真实 DOM

js 复制代码
if (!fiber.dom) {
  fiber.dom = createDom(fiber);
  updateProperties(fiber.dom, fiber.props);
}
  • 为什么要判断 fiber.dom 是否存在?
    因为 fiber 可能已经创建过了(比如中断过一次任务)。
    不判断就会重复创建同一个 DOM。
  • 不这么做会怎样?
    DOM 会被重复添加,导致页面结构错乱或性能浪费。

✨ 2. 挂载 DOM 到容器

js 复制代码
appendDomToContainer(fiber.dom, fiber.parent);

这里抽成了一个函数:appendDomToContainer

  • 为什么要抽离?
    因为副作用逻辑(操作 DOM)最好和计算逻辑分离。
    方便测试、维护,也贴近 React 的「Render + Commit」分阶段思想。
  • 为什么要回退到 root?
    有时候 parentFiber.dom 还没创建好,直接挂 root 避免报错。

✨ 3. 构建 fiber 树关系(child / sibling / parent)

执行完当前 fiber 后,我们要告诉系统下一个该干谁。

js 复制代码
if (fiber.child) return fiber.child;
let nextFiber = fiber;
while (nextFiber) {
  if (nextFiber.sibling) return nextFiber.sibling;
  nextFiber = nextFiber.parent;
}

这段逻辑是 fiber 遍历的精髓:

  • child 优先:深度优先遍历。
  • 没有子节点,就找兄弟节点
  • 没有兄弟,再往上找父节点的兄弟节点

👉 这就是 React Fiber 的「可中断遍历」逻辑。


🧬 五、createNestedFibers ------ 构建测试用 Fiber 树

这是一个生成深层嵌套节点的函数。

我们可以设定 depth,例如 createNestedFibers(100) 表示嵌套 100 层。

js 复制代码
function createNestedFibers(depth = 10) {
  const root = { type: 'div', props: { id: 'test-root', children: [] } };
  let parent = root;

  for (let i = 1; i <= depth; i++) {
    const textFiber = { type: 'TEXT_ELEMENT', props: { nodeValue: `level ${i}` } };
    const divFiber = { type: 'div', props: { id: `node-${i}` } };
    parent.child = textFiber;
    textFiber.sibling = divFiber;
    divFiber.parent = parent;
    parent = divFiber;
  }
  return root;
}
  • 为什么要先建 textFiber 再建 divFiber?
    模拟 React 中的文本 + 元素结构。
  • 为什么循环而不是递归?
    因为递归会占用调用栈,深层嵌套可能导致栈溢出。

🕹️ 六、完整执行流程图

简易图:


🧠 七、知识脑图(Mermaid 结构图)


🔍 八、还有哪些问题?

源代码注释里提到的两点非常关键:

❓1. 如何高效创建大量嵌套 fiber 节点?

可以用循环构造 而不是递归,并在构造过程中建立好 parent/child/sibling 链接。

未来 React 甚至在「创建」这步也会拆任务(Concurrent 模式)。

❓2. 渲染中断后,如何保存状态以便恢复?

我们现在只是"中断了下次从头来",但 React 会在 Fiber 对象中保存工作进度(workInProgress)

下次继续时,就能从上次中断的 fiber 开始,而不是从 root 重新跑。


🌈 九、总结:一图看懂 Fiber 时间切片渲染


🧭 十、思考延伸

  • requestIdleCallback 其实不是 React 的最终方案(因为兼容性差),React 用自己的 Scheduler 调度器 模拟了相同逻辑。

  • Fiber 本质上是"任务分片 + 可中断 + 可恢复"的架构思想。

  • 这套机制还可以扩展出「优先级调度」:

    • 用户输入 > 动画更新 > 低优先渲染。

💬 总结一句话:

Fiber 就像一个"任务分配器",它会在浏览器喘口气时偷偷继续干活,让用户感觉页面一直在流畅地加载

相关推荐
金梦人生2 小时前
UniApp + Vue3 + TS 工程化实战笔记
前端·微信小程序
烟袅2 小时前
深入浏览器渲染流程:从 HTML/CSS/JS 到 60FPS 的视觉魔法
前端·css·html
阿凡达蘑菇灯2 小时前
langgraph---条件边
开发语言·前端·javascript
海云前端12 小时前
别再堆 if-else 了!TypeScript 模式匹配让代码更优雅
前端
RAY_CHEN.2 小时前
vue递归组件-笔记
前端·javascript·vue.js
WenGyyyL2 小时前
GMNER多模态实体识别任务——ReAct结合
前端·react.js·前端框架
晴殇i3 小时前
千万级点赞系统架构演进:从单机数据库到分布式集群的完整解决方案
前端·后端·面试
CDwenhuohuo3 小时前
WebSocket 前端node启用ws调试
前端·websocket·网络协议
Mintopia3 小时前
🤖 具身智能与 WebAIGC 的融合:未来交互技术的奇点漫谈
前端·javascript·aigc