React Fiber 可中断重渲染的核心原理
一、前置背景:React15 重渲染的痛点(为什么需要 Fiber 可中断渲染)
1. React15 核心结构
React15 底层是 纯虚拟DOM树,节点仅通过 props.children 维护「父→子」的单向嵌套关系,无父指针、无兄弟指针。
2. React15 唯一的遍历方式:深度优先递归
因为纯虚拟DOM树的节点,仅知道自己的子节点,不知道父节点/兄弟节点,物理结构锁死了只能用「递归」遍历整棵树,重渲染逻辑如下:
- 任意组件状态变更 → 必须从根节点开始,全量递归遍历整棵虚拟DOM树;
- 组件重渲染 = 递归执行组件逻辑 + 生成新虚拟DOM + 全量diff对比差异;
3. 递归渲染的「致命缺陷」(不可解决)
JavaScript 的递归调用栈是同步、一次性、不可中断的,导致 3 个核心问题:
- ✅ 不可中断:递归一旦启动,必须从根到叶执行到底,期间完全阻塞浏览器主线程;
- ✅ 无进度记录:递归进度存在 JS 引擎的调用栈中,强行终止则进度丢失,恢复只能从头递归;
- ✅ 无优先级调度:所有更新(用户点击/输入、网络请求、定时器)优先级相同,高优先级交互会被低优先级渲染阻塞,页面卡顿、掉帧;
- ✅ 全量更新:无论状态变更范围多小,都要遍历整棵树,性能浪费严重。
二、核心结论:Fiber 可中断重渲染的本质
React16+ 推出 Fiber 架构,核心目标就是解决 React15 递归渲染的所有痛点,实现「可中断、可暂停、可续跑、可优先级调度」的重渲染。
Fiber 可中断重渲染的本质:通过改造数据结构,改变遍历方式,让重渲染的执行过程从「不可中断的递归」变为「可中断的迭代」,配合任务调度实现按需暂停/恢复,最终做到不阻塞浏览器主线程。
三、基础核心:Fiber 树的核心设计(可中断的「物理基础」)
1. Fiber 树的本质:增强版的虚拟DOM树
Fiber 树不是替代虚拟DOM树,而是 在虚拟DOM树的基础上做增量升级,保留了虚拟DOM的所有核心属性(type/props/key等),核心改动只有一个:
✅ 给每个 Fiber 节点新增 3 个核心指针,让节点拥有「完整的导航能力」:
child:指向当前节点的第一个子Fiber节点(对应原虚拟DOM的子节点);return:指向当前节点的父Fiber节点(父指针);sibling:指向当前节点的下一个平级兄弟Fiber节点(兄弟指针)。
2. Fiber 树的核心特性
- 逻辑层面:依然是「树」,和 React 组件树一一映射,完美表达组件的嵌套关系;
- 物理层面:是「带回溯能力的单向链表」,通过
child+return+sibling三个指针,让每个节点都能找到「子、父、兄弟」,彻底摆脱对递归的依赖。
3. 关键细节
Fiber 节点的子节点不再是数组,而是通过链表串联:父节点的child指向第一个子节点,第一个子节点的sibling指向第二个子节点,以此类推,最后一个子节点的sibling = null。
四、核心基石1:遍历方式的质变 - 从「递归」到「迭代」(可中断的核心前提)
1. 核心改变
Fiber 架构下,React 抛弃递归,采用 「深度优先的迭代遍历」 遍历 Fiber 树,遍历的核心依靠 Fiber 节点的三个指针,规则固定且唯一:
当前节点 → 优先遍历 child 子节点 → 无子节点则找 sibling 兄弟节点 → 无兄弟则通过 return 回溯父节点 → 父节点找兄弟节点,循环往复
2. 迭代遍历的核心优势
迭代遍历的所有「遍历进度、节点路径」,都由 React 自身维护的指针变量 记录,与 JS 引擎的调用栈完全解耦。 这是「可中断」的核心前提:递归的不可中断是因为依赖引擎调用栈,而迭代的执行完全由 React 自己掌控,想停就停、想续就续。
五、核心基石2:双缓存 Fiber 树(WorkInProgress + Current)(无副作用更新的保障)
React 内部永久维护 两棵结构完全相同、节点一一映射的 Fiber 树,这是 Fiber 可中断渲染的「安全保障」,所有可中断的更新逻辑都基于此实现,更新过程完全不影响页面真实DOM。
1. Current Fiber Tree(当前树 / 已提交树)
- 作用:精准对应页面上的真实DOM,是「页面当前状态的镜像」;
- 所有节点都是「已完成更新工作」的最终状态,无任何待执行任务;
- 组件的当前状态、真实DOM节点都挂载在这棵树的节点上。
2. WorkInProgress Fiber Tree(工作树 / 草稿树)
- 作用:专门用于执行 可中断的重渲染工作 的「临时树」,相当于「草稿纸」;
- 结构和 Current 树完全同构,节点通过
alternate指针一一对应(互相指向对方,互为替身); - ✅ 核心规则:所有的重渲染逻辑(组件函数执行、hooks链式调用、类组件生命周期、生成虚拟DOM),全部在 WorkInProgress 树上执行,完全不触碰 Current 树和真实DOM。
3. 双缓存树的核心价值
- 可中断更新的「副作用隔离」:更新过程中即使暂停/中断,Current 树依然完好,页面不会出现残缺、错误的DOM状态;
- 复用节点性能优化:通过
alternate指针复用已有节点的状态和属性,避免重复创建节点; - 批量提交更新:所有更新完成后,一次性把差异更新到真实DOM,保证DOM更新的原子性。
六、核心核心:可中断重渲染的核心实现(四大核心机制,缺一不可)
机制一:最小工作单元化 - Fiber 节点 = 最小更新单位
React 把「整棵树的重渲染工作」,拆分成一个一个独立的、最小的工作单元 —— 每一个 Fiber 节点就是一个「最小工作单元」。
- 一个工作单元的执行内容:只处理「当前单个 Fiber 节点」对应的组件重渲染逻辑;
- 函数组件:执行组件函数体 + 按顺序调用 hooks 链 + 生成当前组件的虚拟DOM;
- 类组件:执行更新阶段生命周期 + 执行 render 方法 + 生成当前组件的虚拟DOM;
- 每一次只执行「一个工作单元」,执行完成后就做一次「是否中断」的判断,这是「可中断」的最小粒度保障。
机制二:进度记录 - 全局指针 nextUnitOfWork(核心中的核心)
React 内部维护一个 全局唯一的指针变量 nextUnitOfWork,这是 Fiber 记录「重渲染进度」的唯一方式,无任何复杂逻辑:
nextUnitOfWork的值 = 当前待执行更新工作的 Fiber 节点;- 进度的本质:指针指向哪个 Fiber 节点,重渲染的进度就停在哪个节点;
- 初始化:状态变更后,React 把「状态变更的根 Fiber 节点」赋值给
nextUnitOfWork,作为遍历起点; - 进度更新:每执行完一个 Fiber 节点(一个工作单元),就通过
child/return/sibling指针找到「下一个待执行的节点」,赋值给nextUnitOfWork; - 暂停记录:需要中断时,保留当前的
nextUnitOfWork指针不销毁,进度就被永久记录。
机制三:可中断的工作循环 - workLoop(执行引擎)
React 有一个无限循环的核心执行引擎 workLoop,这是实现「可中断、可续跑」的核心载体,伪代码是 React 源码的核心逻辑,极简且无黑盒:
// Fiber 核心工作循环(核心伪代码)
function workLoop() {
// 只要有「下一个工作单元」,就持续执行
while (nextUnitOfWork !== null) {
// 1. 执行当前 Fiber 节点的更新工作(单个最小工作单元)
// 2. 自动找到下一个待执行的节点,更新 nextUnitOfWork 进度指针
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
// ✅ 可中断核心:执行完一个单元后,立即做「让出判断」
if (shouldYield()) {
// 记录断点:保留当前进度指针 nextUnitOfWork
break; // 主动暂停工作循环,交出浏览器主线程
}
}
// 所有工作单元执行完毕(nextUnitOfWork = null)→ 提交更新
if (nextUnitOfWork === null) {
commitRoot();
}
}
核心判断函数 shouldYield() 让出规则
该函数决定「是否需要暂停重渲染」,判断依据只有2点,优先级从高到低:
- 是否有 更高优先级的任务:用户交互(点击/输入/滚动)> 动画渲染 > 网络请求回调 > 普通数据更新;
- 是否耗尽 浏览器当前帧的空闲时间:React 通过「时间分片」控制,每帧只占用少量时间执行渲染,不抢占浏览器的绘制、布局时间。
机制四:断点续跑 + 优先级调度(可中断的最终落地能力)
断点续跑(无缝恢复渲染)
当浏览器完成高优先级任务(如用户输入)、主线程进入空闲状态时,React 会重新启动 workLoop 工作循环。
此时 nextUnitOfWork 指针依然指向「上一次暂停的 Fiber 节点」,React 直接从该节点继续执行后续的工作单元,无需从头遍历,无缝续跑,进度无丢失。
优先级调度(插队更新,体验优先)
可中断渲染的终极价值是「优先级调度」,当更高优先级的更新任务到来时(如用户点击按钮):
- React 会立即废弃当前正在执行的低优先级 WorkInProgress 树(无副作用,因为未更新真实DOM);
- 基于最新的 Current 树,重新创建新的 WorkInProgress 树;
- 把高优先级更新的「根 Fiber 节点」赋值给
nextUnitOfWork,重置进度指针; - 优先执行高优先级的更新工作,低优先级任务等待浏览器空闲后再续跑/重新执行;
核心:优先级调度的本质是「高优先级任务插队,低优先级任务暂停/废弃」,保证用户能感知的交互永远流畅。
七、关键收尾:Commit 提交阶段(不可中断的最终更新)
当所有需要更新的 Fiber 工作单元执行完毕,nextUnitOfWork 变为 null,此时进入 commit 阶段:
- 该阶段的核心:把 WorkInProgress Fiber 树上的「虚拟DOM差异」,一次性更新到浏览器的真实DOM中;
- 该阶段的特性:同步、不可中断 —— DOM 更新是浏览器的核心操作,必须保证原子性,避免页面出现DOM错乱;
- 提交完成后:WorkInProgress Fiber 树「转正」为新的 Current Fiber 树,原来的旧 Current 树被销毁,等待下一次更新;
- 核心关联:重渲染 ≠ DOM 更新 —— 可中断的是「内存中的 Fiber 遍历/组件执行」,真实DOM更新只在 commit 阶段执行一次。
八、Fiber 可中断重渲染的核心链路(完整流程闭环,从状态变更到页面更新)
1. 组件状态变更(useState/useReducer/Redux dispatch)
2. React 基于当前 Current Fiber 树,克隆生成 WorkInProgress Fiber 树
3. 初始化进度指针 nextUnitOfWork = 状态变更的根 Fiber 节点
4. 启动 workLoop 工作循环,依次执行单个 Fiber 工作单元
5. 每执行一个单元 → 判断 shouldYield() → 需暂停则保留指针、交出主线程
6. 浏览器空闲/高优先级任务完成 → 重启 workLoop,从断点续跑
7. 所有工作单元执行完毕(nextUnitOfWork = null)
8. 进入 commit 阶段,批量更新真实DOM,无中断
9. WorkInProgress 树转正为新的 Current 树,更新完成
九、核心知识点总结
核心原理一句话总结
Fiber 可中断重渲染,是通过「Fiber树的指针改造实现迭代遍历」+「最小工作单元拆分」+「全局指针记录进度」+「可中断工作循环」+「优先级调度」,让 React 能在渲染过程中按需暂停/恢复,不阻塞主线程,实现流畅的并发渲染。
三大核心关键点
- 可中断的物理基础:Fiber 树多了
return(父)、sibling(兄弟)指针,遍历方式从递归 → 迭代; - 可中断的核心保障:双缓存树隔离更新,所有渲染逻辑在草稿树执行,不影响真实DOM;
- 可中断的核心实现:
nextUnitOfWork记录进度 +workLoop循环执行 +shouldYield()控制暂停/续跑。
重要补充
- Fiber 是「单线程」的:所有渲染、更新逻辑都在浏览器主线程执行,并发不是多线程,而是「任务调度层面的并发」;
- React18 的并发特性(Suspense/useTransition):全部基于 Fiber 可中断渲染的底层机制实现,只是上层API封装,核心原理不变;
- 可中断的范围:仅「渲染阶段(render)」可中断,「提交阶段(commit)」不可中断。