核心原理:可中断的增量更新
Fiber 架构的核心目标是将同步的、不可中断的递归渲染,改为异步的、可中断的增量更新。这依赖于两个基础:
- 链表数据结构:将树形结构转换为链表,使遍历可以暂停和恢复。
- 循环模拟递归:用循环代替递归,在每次循环后检查是否有更高优先级任务需要处理。
浏览器一帧内都做了什么事理想情况下,一帧应在16.6ms内完成(1000ms/60fps)
- 事件处理
- requestAnimationFrame回调(在每帧开始前执行,这是浏览器推荐的动画执行时机)
- JavaScript执行
- 样式计算
- 布局
- 绘制
- 合成
- 提交到GPU
- requestIdleCallback,浏览器一帧内任务完成后还有空闲时间执行
伪代码
以下是实现上述流程的最核心的伪代码。
1. 调度器:控制渲染任务循环
调度器是大脑,负责在浏览器空闲时执行任务,并在时间片用完时中断。
// 下一个要处理的工作单元(Fiber节点)
let nextUnitOfWork = null;
// 主循环,由浏览器的 requestIdleCallback 驱动
function workLoop(deadline) {
// 当还有任务,且当前帧还有剩余空闲时间,就继续工作
while (nextUnitOfWork && deadline.timeRemaining() > 1) {
// performUnitOfWork 处理当前节点,并返回下一个节点
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
// 如果任务没做完,但时间用完了,就中断本次渲染,下次空闲时继续
if (nextUnitOfWork) {
requestIdleCallback(workLoop);
} else {
// 如果所有节点都处理完了,就进入不可中断的提交阶段,更新DOM
commitRoot();
}
}
// 启动第一次调度
requestIdleCallback(workLoop);
2. 工作单元:如何处理单个Fiber节点
这是渲染阶段的核心,负责对单个Fiber节点进行深度优先遍历。
// 处理单个Fiber节点,并返回下一个待处理的节点
function performUnitOfWork(fiber) {
// 0. 在这个函数中进行了diff
// 1. 开始工作:处理当前节点(如执行函数组件、打上副作用标记等)
beginWork(fiber);
// 2. 优先返回子节点,进行深度优先遍历
if (fiber.child) {
return fiber.child;
}
// 3. 如果没有子节点,则尝试返回兄弟节点或回溯到父节点
let currentFiber = fiber;
while (currentFiber) {
// 完成当前节点的工作(如创建DOM实例、收集副作用)
completeWork(currentFiber);
// 先找兄弟节点
if (currentFiber.sibling) {
return currentFiber.sibling;
}
// 没有兄弟节点,则回溯到父节点
currentFiber = currentFiber.return;
}
// 遍历完成,返回null
return null;
}
特点
- 可中断:将整个渲染过程分解为以
Fiber节点为单位的任务。 - 增量渲染:通过
workLoop循环,在浏览器空闲时间片内分批执行这些任务。 - 恢复能力:利用Fiber节点的
child,sibling,return指针构成的链表结构,记住遍历进度,从而可以从中断处继续。
旧架构了解
React 15 及之前版本使用的栈调和(Stack Reconciler)
核心原理:同步递归,不可中断
其最根本的特征是:更新过程是深度优先的同步递归,一旦开始,就无法中断。这会导致如果组件树层次很深或计算复杂,JavaScript 线程会长时间占用主线程,造成页面卡顿或无响应。
伪代码
1. 协调器(Reconciler):递归进行 Diff
这是最核心的部分,负责递归遍历整个组件树,找出差异。
// 协调器:递归更新整棵树(不可中断)
function reconcile(component) {
// 1. 处理当前组件:生成新虚拟DOM,并与旧的进行Diff
const oldVNode = component._currentVNode;
const newVNode = component.render(); // 调用render方法
// 简化的Diff逻辑
if (oldVNode.type !== newVNode.type) {
// 类型不同,直接替换整个组件
replaceComponent(component, newVNode);
} else {
// 类型相同,更新属性,然后递归比较子节点
updateProps(component, newVNode.props);
reconcileChildren(component, newVNode.children);
}
// 更新当前虚拟DOM的引用
component._currentVNode = newVNode;
}
// 递归比较子节点
function reconcileChildren(parentComponent, newChildren) {
const oldChildren = parentComponent._currentVNode.children;
// 遍历子节点数组,逐个递归调用reconcile
for (let i = 0; i < newChildren.length; i++) {
const oldChild = oldChildren[i];
const newChild = newChildren[i];
// 递归调用!一旦开始,无法停止。
reconcile(getComponentForVNode(oldChild, newChild));
}
}
2. 渲染器(Renderer):同步更新 DOM
协调器完成某个节点的 Diff 后,会立即通知渲染器更新真实 DOM,两者是交替工作的。
// 渲染器:将差异同步应用到真实DOM
function replaceComponent(oldComponent, newVNode) {
// 1. 卸载旧组件
oldComponent._domNode.remove();
// 2. 创建并挂载新组件
const newDomNode = createDomFromVNode(newVNode);
oldComponent._parentNode.appendChild(newDomNode);
}