React Fiber 深度解析
目录
什么是 React Fiber - 具像化理解
用餐厅类比理解 Fiber
想象一个繁忙的餐厅:
React 15 之前(Stack Reconciler)
- 👨🍳 厨师接到一个大桌订单(大组件树更新)
- 🔒 必须一口气做完所有菜(同步递归)
- ⏱️ 其他客人的简单需求也要等待(阻塞渲染)
- 😰 VIP 客人的紧急请求无法优先处理(无法中断)
React 16+ (Fiber Reconciler)
- 👨🍳 厨师把大订单拆分成一道道菜(任务分片)
- ⏸️ 做完一道菜可以暂停,处理 VIP 的紧急需求(可中断)
- 📋 维护一个优先级队列(任务调度)
- 🔄 做到一半可以切换任务,之后继续(恢复执行)
核心概念具像化
传统 Stack Reconciler(递归):
┌─────────────────────────────────────┐
│ 更新开始 │
│ ↓ │
│ 递归遍历整棵树 (不可中断) │
│ ↓ │
│ DOM 更新 │
│ ↓ │
│ 完成 │
└─────────────────────────────────────┘
问题: 长时间占用主线程 → 页面卡顿
Fiber Reconciler(链表):
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 任务 1 │ → │ 任务 2 │ → │ 任务 3 │
│ (可中断) │ │ (可中断) │ │ (可中断) │
└──────────┘ └──────────┘ └──────────┘
↓ ↓ ↓
检查时间 检查时间 检查时间
↓ ↓ ↓
有剩余时间? 有剩余时间? 有剩余时间?
Yes → 继续 Yes → 继续 No → 让出控制权
Fiber 的三重含义
- 作为架构 - 一种全新的协调引擎
- 作为数据结构 - 组件的工作单元
- 作为执行单元 - 可暂停、可恢复的计算单位
Fiber 架构原理详解
1. Fiber 节点数据结构
// 简化的 Fiber 节点结构
function FiberNode(tag, pendingProps, key, mode) {
// ===== 实例属性 =====
this.tag = tag; // Fiber 类型标记
this.key = key; // 唯一标识
this.elementType = null; // 元素类型
this.type = null; // 组件类型
this.stateNode = null; // 真实 DOM 节点引用
// ===== Fiber 链表结构 =====
this.return = null; // 父 Fiber (← 向上)
this.child = null; // 第一个子 Fiber (↓ 向下)
this.sibling = null; // 下一个兄弟 Fiber (→ 向右)
this.index = 0; // 在兄弟节点中的索引
// ===== 副作用相关 =====
this.flags = NoFlags; // 副作用标记(增删改)
this.subtreeFlags = NoFlags; // 子树的副作用标记
this.deletions = null; // 需要删除的子 Fiber
// ===== 优先级调度 =====
this.lanes = NoLanes; // 本 Fiber 的优先级
this.childLanes = NoLanes; // 子树的优先级
// ===== 双缓存机制 =====
this.alternate = null; // 指向另一棵树的对应节点
// ===== 状态和属性 =====
this.memoizedProps = null; // 上次渲染的 props
this.memoizedState = null; // 上次渲染的 state
this.pendingProps = pendingProps; // 新的 props
this.updateQueue = null; // 更新队列
}
2. Fiber 树的结构
示例组件树:
<App>
<Header>
<Logo />
</Header>
<Content>
<Sidebar />
<Main />
</Content>
</App>
转换为 Fiber 树:
App (FiberRoot)
│
┌────────┴────────┐
↓ │
Header sibling
(child) ↓
│ Content
↓ │
Logo ┌──────┴──────┐
(return: Header)│ │
↓ sibling
Sidebar ↓
(return: Content) Main
(return: Content)
链接关系:
- child: 父 → 第一个子
- sibling: 兄 → 弟
- return: 子 → 父
3. 双缓存机制 (Double Buffering)
// 双缓存树结构
Current Fiber Tree (当前屏幕显示) WorkInProgress Fiber Tree (内存中构建)
│ │
│ ←──────── alternate ───────────→ │
│ │
┌───┴───┐ ┌───┴───┐
│ App │ ←──── alternate ────────→ │ App │
└───┬───┘ └───┬───┘
│ │
┌───┴────┐ ┌───┴────┐
│ Header │ ←──── alternate ────────→ │ Header │
└────────┘ └────────┘
工作流程:
1. 首次渲染: 创建 WorkInProgress 树
2. 完成后: WorkInProgress 变成 Current
3. 再次更新: 基于 Current 克隆新的 WorkInProgress
4. 复用节点: alternate 指针指向可复用的节点
4. 工作循环 (Work Loop)
// 简化的工作循环逻辑
function workLoopConcurrent() {
// 只要还有工作单元 且 有剩余时间
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
// 1. 开始处理当前 Fiber (递阶段)
let next = beginWork(current, unitOfWork, renderLanes);
// 更新 props
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 2. 没有子节点,完成当前 Fiber (归阶段)
completeUnitOfWork(unitOfWork);
} else {
// 3. 继续处理子节点
workInProgress = next;
}
}
// 时间切片判断
function shouldYield() {
const currentTime = getCurrentTime();
// 超过 5ms 就让出控制权
return currentTime >= deadline;
}
5. 两大阶段详解
Render 阶段(可中断)
// Render 阶段:协调过程,找出变化
function beginWork(current, workInProgress, renderLanes) {
// 根据不同组件类型进行处理
switch (workInProgress.tag) {
case FunctionComponent: {
// 执行函数组件
const Component = workInProgress.type;
const props = workInProgress.pendingProps;
return updateFunctionComponent(
current,
workInProgress,
Component,
props,
renderLanes
);
}
case ClassComponent: {
// 调用类组件的 render
const Component = workInProgress.type;
const instance = workInProgress.stateNode;
return updateClassComponent(
current,
workInProgress,
Component,
renderLanes
);
}
case HostComponent: {
// 原生 DOM 标签
return updateHostComponent(current, workInProgress, renderLanes);
}
// ... 其他类型
}
}
// 完成阶段
function completeWork(current, workInProgress, renderLanes) {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
// 创建或更新 DOM 节点
if (current !== null && workInProgress.stateNode != null) {
// 更新
updateHostComponent(current, workInProgress, type, newProps);
} else {
// 创建
const instance = createInstance(type, newProps, workInProgress);
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
}
break;
}
}
}
Commit 阶段(不可中断)
// Commit 阶段:将变化应用到 DOM
function commitRoot(root) {
const finishedWork = root.finishedWork;
// 1. Before Mutation 阶段 (DOM 操作前)
commitBeforeMutationEffects(finishedWork);
// 2. Mutation 阶段 (执行 DOM 操作)
commitMutationEffects(finishedWork, root);
// 3. 切换 Fiber 树
root.current = finishedWork;
// 4. Layout 阶段 (DOM 操作后)
commitLayoutEffects(finishedWork, root);
}
function commitMutationEffects(root, finishedWork) {
// 遍历 effectList 执行副作用
while (nextEffect !== null) {
const flags = nextEffect.flags;
// 处理 DOM 插入
if (flags & Placement) {
commitPlacement(nextEffect);
}
// 处理 DOM 更新
if (flags & Update) {
commitWork(current, nextEffect);
}
// 处理 DOM 删除
if (flags & Deletion) {
commitDeletion(nextEffect);
}
nextEffect = nextEffect.nextEffect;
}
}
源码深度剖析
1. Fiber 创建流程
// packages/react-reconciler/src/ReactFiber.js
// 创建 Fiber 节点
export function createFiber(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
return new FiberNode(tag, pendingProps, key, mode);
}
// 从元素创建 Fiber
export function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
null,
mode,
lanes,
);
return fiber;
}
// 复用 Fiber (关键优化!)
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// 首次创建 alternate
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 双向链接
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// 复用已存在的 alternate
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
// 重置副作用
workInProgress.flags = NoFlags;
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
}
// 复用大部分属性
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
return workInProgress;
}
2. Diff 算法实现
// packages/react-reconciler/src/ReactChildFiber.js
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
// 1. 处理文本节点
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, lanes)
);
}
// 2. 处理单个元素
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes)
);
}
// 3. 处理数组(多个子元素)
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
}
}
// 删除剩余的旧子节点
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
// 多节点 Diff(最复杂)
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<any>,
lanes: Lanes,
): Fiber | null {
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// ===== 第一轮遍历 =====
// 处理更新的节点(key 和 type 都相同)
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
// 尝试复用节点
const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
// key 不同,跳出第一轮遍历
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// 新节点没有复用旧节点,删除旧节点
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 构建新的 Fiber 链表
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
// ===== 新节点已全部处理完 =====
if (newIdx === newChildren.length) {
// 删除剩余的旧节点
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
// ===== 旧节点已全部处理完 =====
if (oldFiber === null) {
// 创建剩余的新节点
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) continue;
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// ===== 第二轮遍历 =====
// 将剩余的旧节点放入 Map
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// 遍历剩余的新节点
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// 复用了旧节点,从 Map 中删除
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// 删除 Map 中剩余的旧节点
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
3. 调度器 (Scheduler)
// packages/scheduler/src/forks/Scheduler.js
// 优先级定义
export const ImmediatePriority = 1; // 立即执行(最高优先级)
export const UserBlockingPriority = 2; // 用户交互
export const NormalPriority = 3; // 正常优先级
export const LowPriority = 4; // 低优先级
export const IdlePriority = 5; // 空闲时执行
// 不同优先级的超时时间
const maxSigned31BitInt = 1073741823;
const IMMEDIATE_PRIORITY_TIMEOUT = -1;
const USER_BLOCKING_PRIORITY_TIMEOUT = 250;
const NORMAL_PRIORITY_TIMEOUT = 5000;
const LOW_PRIORITY_TIMEOUT = 10000;
const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
// 调度任务
function unstable_scheduleCallback(priorityLevel, callback, options) {
const currentTime = getCurrentTime();
let startTime;
if (typeof options === 'object' && options !== null) {
const delay = options.delay;
startTime = typeof delay === 'number' ? currentTime + delay : currentTime;
} else {
startTime = currentTime;
}
let timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
const expirationTime = startTime + timeout;
const newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (startTime > currentTime) {
// 延迟任务
newTask.sortIndex = startTime;
push(timerQueue, newTask);
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
// 立即执行的任务
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
}
// 工作循环
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
advanceTimers(currentTime);
currentTask = peek(taskQueue);
while (currentTask !== null) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
// 任务未过期且没有剩余时间,中断
break;
}
const callback = currentTask.callback;
if (typeof callback === 'function') {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
// 执行任务
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {
// 任务返回了新的回调,继续执行
currentTask.callback = continuationCallback;
} else {
// 任务完成,从队列中移除
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
advanceTimers(currentTime);
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
// 返回是否还有剩余任务
if (currentTask !== null) {
return true;
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}
4. Lane 模型(优先级)
// packages/react-reconciler/src/ReactFiberLane.js
// Lane 使用二进制位表示优先级
export const NoLanes: Lanes = 0b0000000000000000000000000000000;
export const NoLane: Lane = 0b0000000000000000000000000000000;
export const SyncLane: Lane = 0b0000000000000000000000000000001;
export const InputContinuousHydrationLane: Lane = 0b0000000000000000000000000000010;
export const InputContinuousLane: Lane = 0b0000000000000000000000000000100;
export const DefaultHydrationLane: Lane = 0b0000000000000000000000000001000;
export const DefaultLane: Lane = 0b0000000000000000000000000010000;
const TransitionLanes: Lanes = 0b0000000001111111111111111100000;
const TransitionLane1: Lane = 0b0000000000000000000000000100000;
const TransitionLane2: Lane = 0b0000000000000000000000001000000;
// ... 更多 Transition Lanes
// 找到最高优先级的 Lane
export function getHighestPriorityLane(lanes: Lanes): Lane {
return lanes & -lanes; // 位运算技巧
}
// 合并 Lanes
export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a | b;
}
// 检查是否包含某个 Lane
export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane): boolean {
return (a & b) !== NoLanes;
}
// 移除 Lanes
export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
return set & ~subset;
}
面试高频问题与最优答案
Q1: 什么是 React Fiber?为什么要引入 Fiber 架构?
最优答案:
React Fiber 是 React 16 引入的新协调引擎,它主要解决了以下问题:
问题背景: 在 React 15 及之前,使用 Stack Reconciler(栈调和器),采用递归方式更新组件树。一旦开始就无法中断,如果组件树很大,会长时间占用主线程,导致:
- 用户交互(如输入)卡顿
- 动画掉帧
- 页面响应延迟
Fiber 解决方案:
-
时间切片(Time Slicing)
- 将长任务拆分成多个小任务
- 每个任务执行约 5ms
- 执行完一个任务检查是否有更高优先级任务
- 有则暂停当前任务,执行高优先级任务
-
可中断的更新
- Fiber 是一个链表结构,可以暂停和恢复
- 通过
workInProgress指针记录当前位置 - 可以随时中断,去处理更紧急的事情
-
优先级调度
- 不同更新有不同优先级
- 用户交互(点击、输入)优先级最高
- 网络请求、动画次之
- 后台任务优先级最低
核心数据结构:
{
// 链表结构
return: Fiber, // 父节点
child: Fiber, // 第一个子节点
sibling: Fiber, // 下一个兄弟节点
// 双缓存
alternate: Fiber, // 另一棵树的对应节点
// 副作用
flags: Flags, // 增删改标记
// 优先级
lanes: Lanes, // 任务优先级
}
Q2: Fiber 的工作原理是什么?详细说说 Render 和 Commit 阶段
最优答案:
Fiber 的工作分为两个主要阶段:
Render 阶段(可中断,纯净无副作用)
目的:找出需要变化的节点,打上标记
过程:
-
递阶段(beginWork)
function beginWork(current, workInProgress) { // 1. 根据不同组件类型处理 switch (workInProgress.tag) { case FunctionComponent: // 执行函数,获取 children const children = Component(props); reconcileChildren(current, workInProgress, children); break; case ClassComponent: // 调用 render 方法 const instance = workInProgress.stateNode; const children = instance.render(); reconcileChildren(current, workInProgress, children); break; } // 2. 返回第一个子节点,继续遍历 return workInProgress.child; } -
归阶段(completeWork)
function completeWork(current, workInProgress) { // 1. 创建或标记更新 DOM 节点 // 2. 收集 flags(副作用标记) // 3. 形成 effectList 链表 // 没有子节点了,回到父节点 if (workInProgress.sibling) { return workInProgress.sibling; // 处理兄弟节点 } return workInProgress.return; // 回到父节点 }
遍历顺序示例:
A
/ \
B C
/ \
D E
遍历顺序: A -> B -> D -> E -> B -> C -> A
递: A -> B -> D
归: D -> E -> B -> C -> A
可中断机制:
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function shouldYield() {
// 当前时间超过截止时间,让出控制权
return getCurrentTime() >= deadline;
}
Commit 阶段(不可中断,同步执行)
目的:将 Render 阶段的结果应用到 DOM
三个子阶段:
-
Before Mutation(DOM 操作前)
function commitBeforeMutationEffects() { // 1. 调用 getSnapshotBeforeUpdate // 2. 异步调度 useEffect } -
Mutation(DOM 操作)
function commitMutationEffects() { while (nextEffect !== null) { const flags = nextEffect.flags; // 插入 DOM if (flags & Placement) { commitPlacement(nextEffect); } // 更新 DOM if (flags & Update) { commitWork(nextEffect); } // 删除 DOM if (flags & Deletion) { commitDeletion(nextEffect); } nextEffect = nextEffect.nextEffect; } } -
Layout(DOM 操作后)
function commitLayoutEffects() { // 1. 调用 componentDidMount/Update // 2. 调用 useLayoutEffect // 3. 更新 ref }
为什么 Commit 不可中断?
- DOM 操作必须原子性完成
- 避免用户看到中间状态
- 保证生命周期顺序正确
Q3: Fiber 的 Diff 算法有什么优化?
最优答案:
React Fiber 的 Diff 算法基于三个假设,进行了针对性优化:
三大策略
-
Tree Diff(树层级)
- 只比较同层级节点
- 不同层级的节点,直接删除重建
- 时间复杂度:O(n)
-
Component Diff(组件级别)
- 同类型组件:继续比较
- 不同类型:直接替换
- shouldComponentUpdate 优化
-
Element Diff(元素级别)
- 使用 key 优化列表对比
- 三种操作:插入、移动、删除
多节点 Diff 详解(最复杂)
场景:列表从 [A, B, C] 更新为 [B, A, D, C]
第一轮遍历:处理更新节点
// 从左向右遍历
oldFibers: A B C
newChildren: B A D C
^
// A 和 B 的 key 不同,跳出第一轮
第二轮遍历:处理剩余节点
// 1. 将剩余旧节点放入 Map
existingChildren = {
A: FiberA,
B: FiberB,
C: FiberC
}
// 2. 遍历剩余新节点
newChildren: B A D C
// B 在 Map 中找到 -> 复用,标记移动
// A 在 Map 中找到 -> 复用,标记移动
// D 在 Map 中找不到 -> 新建
// C 在 Map 中找到 -> 复用
// 3. Map 中剩余的节点 -> 删除
最长递增子序列优化(移动次数最少)
旧: 0 1 2 3 4 5
新: 2 1 3 4 5 0
lastPlacedIndex 算法:
- 维护一个 lastPlacedIndex,记录最后一个不需要移动的节点位置
- 新节点的 oldIndex < lastPlacedIndex,则需要移动
- 否则更新 lastPlacedIndex
结果: 只需要移动 2,其他节点不动
Key 的重要性
// 没有 key - 每次都重新创建
{items.map(item => <Item data={item} />)}
// 有 key - 可以复用
{items.map(item => <Item key={item.id} data={item} />)}
Key 的最佳实践:
- ✅ 使用稳定的唯一标识(如 id)
- ❌ 不要使用 index(顺序变化时会出问题)
- ❌ 不要使用 Math.random()(每次都变)
Q4: 什么是双缓存机制?为什么需要它?
最优答案:
双缓存(Double Buffering)是 Fiber 架构的核心机制之一。
概念
React 维护两棵 Fiber 树:
- current 树:当前屏幕显示的内容
- workInProgress 树:正在内存中构建的新树
Current Tree WorkInProgress Tree
│ │
│ ←──── alternate ──────→ │
│ │
┌───┴───┐ ┌───┴───┐
│ App │ ←──alternate────→ │ App │
└───┬───┘ └───┬───┘
│ │
子 Fiber 子 Fiber
工作流程
-
首次渲染
// 1. 创建 FiberRoot const root = createFiberRoot(container); // 2. 创建 rootFiber (workInProgress) const uninitializedFiber = createHostRootFiber(); root.current = uninitializedFiber; // 3. 构建 workInProgress 树 // 4. 完成后,将 workInProgress 变成 current root.current = finishedWork; -
更新阶段
// 1. 基于 current 创建 workInProgress function beginWork(current, workInProgress) { if (current !== null) { // 能复用的节点,直接复制 const oldProps = current.memoizedProps; const newProps = workInProgress.pendingProps; if (oldProps === newProps && !hasContextChanged()) { // 复用整个子树 return bailoutOnAlreadyFinishedWork(current, workInProgress); } } // 不能复用,重新创建 return updateFunctionComponent(current, workInProgress, Component); } // 2. 完成后切换 root.current = root.current.alternate;
为什么需要双缓存?
-
避免中间状态
- Render 阶段可中断,可能被打断多次
- 如果直接修改 current 树,用户会看到不完整的 UI
- 在内存中构建完整的树,一次性切换
-
复用节点
function createWorkInProgress(current, pendingProps) { let workInProgress = current.alternate; if (workInProgress === null) { // 首次创建 workInProgress = createFiber(current.tag, pendingProps); workInProgress.alternate = current; current.alternate = workInProgress; } else { // 复用 alternate workInProgress.pendingProps = pendingProps; workInProgress.flags = NoFlags; } // 复用属性 workInProgress.child = current.child; workInProgress.memoizedProps = current.memoizedProps; workInProgress.memoizedState = current.memoizedState; return workInProgress; } -
性能优化
- 不需要每次都创建新对象
- 两棵树来回切换,减少内存开销
类比理解
就像视频播放:
- 单缓冲:直接在屏幕上绘制 → 会看到绘制过程(闪烁)
- 双缓冲:先在后台绘制完整帧 → 一次性显示(流畅)
Q5: Lane 模型是什么?与 expirationTime 有什么区别?
最优答案:
Lane 是 React 17 引入的新优先级模型,替代了之前的 expirationTime。
expirationTime 的问题(React 16)
// 旧方式:使用过期时间表示优先级
const expirationTime = currentTime + timeout;
// 问题 1: 优先级不够细
// 只能通过 timeout 区分,缺乏灵活性
// 问题 2: 合并困难
// 两个任务的 expirationTime 可能不同
// 难以批量处理
// 问题 3: 优先级不可组合
// 一个更新只能有一个优先级
Lane 模型(React 17+)
使用二进制位表示优先级,每一位代表一个"车道":
// Lane 定义(简化版)
const SyncLane = 0b0000000000000000000000000000001; // 同步
const InputContinuousLane = 0b0000000000000000000000000000100; // 连续输入
const DefaultLane = 0b0000000000000000000000000010000; // 默认
const TransitionLane1 = 0b0000000000000000000000000100000; // 过渡 1
const TransitionLane2 = 0b0000000000000000000000001000000; // 过渡 2
// ... 更多 Lanes
// 优先级从右到左递减(越右优先级越高)
Lane 的优势
-
更细粒度的优先级
// 可以表示 31 种不同的优先级 // 每个 bit 是一个独立的"车道" -
可以组合
// 位运算合并多个优先级 const lanes = SyncLane | InputContinuousLane; // 0b0000000000000000000000000000101 // 检查是否包含某个优先级 if (lanes & SyncLane) { // 包含同步任务 } // 移除某个优先级 const remaining = lanes & ~SyncLane; -
批量处理
// 多个相同优先级的更新可以批量处理 const updateLanes = TransitionLane1 | TransitionLane2; // 一次性处理所有 Transition 更新 if (updateLanes & TransitionLanes) { processTransitionUpdates(); } -
更好的性能
// 位运算比数值比较更快 // 找到最高优先级 function getHighestPriorityLane(lanes) { return lanes & -lanes; // 获取最右边的 1 } // 示例 getHighestPriorityLane(0b0101000) // 0b0001000
Lane 的实际应用
// 1. 调度更新
function scheduleUpdateOnFiber(fiber, lane) {
// 标记 fiber 的优先级
fiber.lanes = mergeLanes(fiber.lanes, lane);
// 向上标记父节点
let parent = fiber.return;
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
parent = parent.return;
}
// 调度根节点更新
ensureRootIsScheduled(root);
}
// 2. 跳过低优先级更新
function beginWork(current, workInProgress, renderLanes) {
const updateLanes = workInProgress.lanes;
// 当前渲染的优先级不包含此 fiber 的更新
if (!includesSomeLane(renderLanes, updateLanes)) {
// 跳过此节点,复用旧状态
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// 处理更新
// ...
}
// 3. 优先级降级
function markStarvedLanesAsExpired(root, currentTime) {
const pendingLanes = root.pendingLanes;
const expirationTimes = root.expirationTimes;
let lanes = pendingLanes;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
const expirationTime = expirationTimes[index];
if (expirationTime === NoTimestamp) {
// 设置过期时间
expirationTimes[index] = computeExpirationTime(lane, currentTime);
} else if (expirationTime <= currentTime) {
// 已过期,提升到同步优先级
root.expiredLanes |= lane;
}
lanes &= ~lane;
}
}
对比总结
| 特性 | expirationTime | Lane |
|---|---|---|
| 实现方式 | 时间戳 | 二进制位 |
| 优先级数量 | 有限 | 31 种 |
| 可组合性 | ❌ | ✅ |
| 性能 | 数值比较 | 位运算(更快) |
| 批处理 | 困难 | 容易 |
| 灵活性 | 低 | 高 |
Q6: Fiber 如何实现可中断的渲染?requestIdleCallback 的问题是什么?
最优答案:
可中断渲染的核心机制
-
时间切片(Time Slicing)
let yieldInterval = 5; // 每 5ms 让出控制权 let deadline = 0; function workLoopConcurrent() { // 循环执行任务 while (workInProgress !== null && !shouldYield()) { performUnitOfWork(workInProgress); } } function shouldYield() { // 检查是否应该让出控制权 const currentTime = getCurrentTime(); return currentTime >= deadline; } -
工作单元拆分
function performUnitOfWork(unitOfWork) { const current = unitOfWork.alternate; // 1. beginWork - 处理当前节点 let next = beginWork(current, unitOfWork, renderLanes); if (next === null) { // 2. completeWork - 完成当前节点 completeUnitOfWork(unitOfWork); } else { // 3. 继续处理子节点 workInProgress = next; } } -
保存中断状态
// 通过链表结构保存进度 let workInProgress = null; // 当前处理的 Fiber let workInProgressRoot = null; // 当前根节点 let workInProgressRootRenderLanes = NoLanes; // 当前优先级 // 中断时,这些变量保存了当前状态 // 恢复时,从 workInProgress 继续
requestIdleCallback 的问题
React 最初想使用 requestIdleCallback,但发现存在问题:
-
兼容性差
// Safari 不支持 if (typeof requestIdleCallback === 'function') { // 支持 } else { // 不支持,需要 polyfill } -
执行频率不稳定
// 浏览器空闲时才执行 requestIdleCallback((deadline) => { while (deadline.timeRemaining() > 0) { // 执行任务 } }); // 问题: 如果浏览器一直忙,任务可能永远不执行 // 用户交互密集时,空闲时间很少 -
优先级不受控
// requestIdleCallback 只有一个优先级 // 无法区分紧急任务和普通任务 requestIdleCallback(lowPriorityTask); // 需要更细粒度的优先级控制 -
帧率限制
// requestIdleCallback 在每帧结束后调用 // FPS 60: 每帧 16.6ms // 如果执行时间 > 16.6ms,会影响下一帧 // React 需要更灵活的时间分片
React 自己的 Scheduler 实现
// packages/scheduler/src/forks/Scheduler.js
// 1. 使用 MessageChannel 模拟
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
function requestHostCallback(callback) {
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
port.postMessage(null);
}
}
// 2. 时间切片实现
function performWorkUntilDeadline() {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// 设置 deadline (5ms 后)
deadline = currentTime + yieldInterval;
const hasTimeRemaining = true;
let hasMoreWork = true;
try {
// 执行工作
hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
} finally {
if (hasMoreWork) {
// 还有工作,继续调度
port.postMessage(null);
} else {
isMessageLoopRunning = false;
scheduledHostCallback = null;
}
}
}
}
// 3. 优先级队列
let taskQueue = []; // 普通任务队列
let timerQueue = []; // 延时任务队列
function unstable_scheduleCallback(priorityLevel, callback, options) {
const currentTime = getCurrentTime();
let startTime;
if (typeof options === 'object' && options !== null) {
const delay = options.delay;
startTime = typeof delay === 'number' ? currentTime + delay : currentTime;
} else {
startTime = currentTime;
}
let timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = -1;
break;
case UserBlockingPriority:
timeout = 250;
break;
case IdlePriority:
timeout = maxSigned31BitInt;
break;
case LowPriority:
timeout = 10000;
break;
case NormalPriority:
default:
timeout = 5000;
break;
}
const expirationTime = startTime + timeout;
const newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (startTime > currentTime) {
// 延时任务
newTask.sortIndex = startTime;
push(timerQueue, newTask);
} else {
// 立即任务
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
requestHostCallback(flushWork);
}
return newTask;
}
为什么用 MessageChannel?
// 1. setTimeout/setInterval - 有最小延迟(4ms)
setTimeout(() => {
// 至少延迟 4ms
}, 0);
// 2. requestAnimationFrame - 帧率限制
requestAnimationFrame(() => {
// 每帧执行一次(16.6ms @ 60fps)
});
// 3. MessageChannel - 没有延迟,没有帧率限制
const channel = new MessageChannel();
channel.port1.onmessage = () => {
// 立即执行,不受帧率限制
};
channel.port2.postMessage(null);
// 而且 MessageChannel 是宏任务
// 可以让微任务(Promise)先执行
完整的中断恢复流程
function renderRootConcurrent(root, lanes) {
// 1. 准备工作
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
// 新的渲染任务
prepareFreshStack(root, lanes);
}
// 2. 工作循环
do {
try {
workLoopConcurrent();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// 3. 检查状态
if (workInProgress !== null) {
// 被中断了,返回 in progress
return RootInProgress;
} else {
// 完成了,返回完成状态
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
}
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
// workInProgress 保存了中断位置
// 下次继续从这里开始
}
Q7: 说说 Fiber 的生命周期变化
最优答案:
Fiber 架构导致部分生命周期被废弃或重命名。
为什么要改变生命周期?
Render 阶段可能被中断和重启,导致某些生命周期可能被多次调用:
// Render 阶段 (可能被多次调用)
constructor()
getDerivedStateFromProps()
shouldComponentUpdate()
render()
// Commit 阶段 (只调用一次)
getSnapshotBeforeUpdate()
componentDidMount()
componentDidUpdate()
componentWillUnmount()
废弃的生命周期(不安全)
class Component {
// ❌ 被废弃 - Render 阶段,可能执行多次
componentWillMount() {
// 问题: 可能多次调用,导致:
// - 多次发送网络请求
// - 多次注册事件监听
// - 副作用执行多次
this.fetchData(); // 危险!
}
// ❌ 被废弃 - Render 阶段,可能执行多次
componentWillReceiveProps(nextProps) {
// 问题: props 没变也可能触发
if (this.props.id !== nextProps.id) {
this.fetchData(nextProps.id); // 危险!
}
}
// ❌ 被废弃 - Render 阶段,可能执行多次
componentWillUpdate(nextProps, nextState) {
// 问题: 可能多次调用
this.prepareUpdate(); // 危险!
}
}
新的生命周期
class Component {
// ✅ 替代 componentWillReceiveProps
static getDerivedStateFromProps(props, state) {
// 1. 静态方法,没有 this,避免副作用
// 2. 每次渲染都会调用
// 3. 返回新 state 或 null
if (props.id !== state.prevId) {
return {
prevId: props.id,
data: null, // 重置数据
};
}
return null;
}
// ✅ 在 commit 阶段,DOM 更新前调用
getSnapshotBeforeUpdate(prevProps, prevState) {
// 获取更新前的信息(如滚动位置)
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
// ✅ snapshot 是上面返回的值
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
}
完整的生命周期流程
// 1. 挂载阶段
constructor() // 创建实例
↓
static getDerivedStateFromProps() // 从 props 派生 state
↓
render() // 渲染
↓
React 更新 DOM 和 refs
↓
componentDidMount() // 挂载完成
// 2. 更新阶段
New props / setState() / forceUpdate()
↓
static getDerivedStateFromProps() // 从 props 派生 state
↓
shouldComponentUpdate() // 是否需要更新?
↓
render() // 渲染
↓
getSnapshotBeforeUpdate() // 获取更新前快照
↓
React 更新 DOM 和 refs
↓
componentDidUpdate() // 更新完成
// 3. 卸载阶段
componentWillUnmount() // 卸载
迁移指南
// 旧代码
class OldComponent extends React.Component {
componentWillMount() {
this.fetchData();
}
componentWillReceiveProps(nextProps) {
if (this.props.id !== nextProps.id) {
this.fetchData(nextProps.id);
}
}
}
// 新代码
class NewComponent extends React.Component {
state = {
data: null,
prevId: this.props.id,
};
// 使用静态方法派生 state
static getDerivedStateFromProps(props, state) {
if (props.id !== state.prevId) {
return {
prevId: props.id,
data: null,
};
}
return null;
}
// 副作用放到 componentDidMount/Update
componentDidMount() {
this.fetchData(this.props.id);
}
componentDidUpdate(prevProps) {
if (this.props.id !== prevProps.id) {
this.fetchData(this.props.id);
}
}
fetchData(id) {
// 发送请求
}
}
// 或者使用 Hooks (推荐)
function NewComponent({ id }) {
const [data, setData] = useState(null);
useEffect(() => {
fetchData(id).then(setData);
}, [id]); // id 变化时重新请求
return <div>{data}</div>;
}
Q8: Fiber 对 React Hooks 的影响
最优答案:
Hooks 是基于 Fiber 架构实现的,每个 Hook 对应 Fiber 节点上的一个链表节点。
Hook 数据结构
// 每个 Hook 是一个对象
const hook = {
memoizedState: null, // 当前 state 值
baseState: null, // 基础 state
baseQueue: null, // 基础更新队列
queue: null, // 更新队列
next: null, // 下一个 Hook
};
// Hooks 形成链表
function FunctionComponent() {
const [state1, setState1] = useState(0); // hook1
const [state2, setState2] = useState(0); // hook2
const value = useMemo(() => {}, []); // hook3
useEffect(() => {}, []); // hook4
// Fiber.memoizedState -> hook1 -> hook2 -> hook3 -> hook4
}
为什么不能在条件语句中使用 Hooks?
// ❌ 错误示例
function BadComponent({ condition }) {
const [state1, setState1] = useState(0); // hook1
if (condition) {
const [state2, setState2] = useState(0); // hook2 (可能存在)
}
const [state3, setState3] = useState(0); // hook3 或 hook2
// 问题:
// 首次渲染: condition = true
// hook1 -> hook2 -> hook3
// 再次渲染: condition = false
// hook1 -> hook3
// React 期望 hook2,但实际是 hook3,类型不匹配!
}
// ✅ 正确示例
function GoodComponent({ condition }) {
const [state1, setState1] = useState(0); // hook1
const [state2, setState2] = useState(0); // hook2
const [state3, setState3] = useState(0); // hook3
// 条件逻辑放在 Hook 内部
useEffect(() => {
if (condition) {
// ...
}
}, [condition]);
}
Hook 的执行原理
// 简化的 Hook 实现
let currentlyRenderingFiber = null; // 当前渲染的 Fiber
let currentHook = null; // 当前 Hook
let workInProgressHook = null; // 工作中的 Hook
// useState 实现
function useState(initialState) {
return useReducer(
basicStateReducer,
initialState,
);
}
function useReducer(reducer, initialArg, init) {
const fiber = currentlyRenderingFiber;
let hook;
if (currentHook === null) {
// Mount 阶段 - 创建新 Hook
hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: {
pending: null,
dispatch: null,
},
next: null,
};
if (workInProgressHook === null) {
// 第一个 Hook
fiber.memoizedState = workInProgressHook = hook;
} else {
// 添加到链表
workInProgressHook = workInProgressHook.next = hook;
}
// 初始化 state
hook.memoizedState = hook.baseState = initialArg;
} else {
// Update 阶段 - 复用 Hook
hook = currentHook;
workInProgressHook.next = hook;
currentHook = currentHook.next;
// 处理更新队列
if (hook.queue.pending !== null) {
const update = hook.queue.pending;
// 计算新 state
const newState = reducer(hook.memoizedState, update.action);
hook.memoizedState = newState;
}
}
// 创建 dispatch 函数
const dispatch = dispatchAction.bind(null, fiber, hook.queue);
hook.queue.dispatch = dispatch;
return [hook.memoizedState, dispatch];
}
// dispatch 实现
function dispatchAction(fiber, queue, action) {
// 创建更新对象
const update = {
action,
next: null,
};
// 加入更新队列
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
// 调度更新
scheduleUpdateOnFiber(fiber, lane);
}
useEffect 的实现
function useEffect(create, deps) {
const fiber = currentlyRenderingFiber;
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 标记副作用
fiber.flags |= PassiveEffect;
// 创建 effect 对象
hook.memoizedState = pushEffect(
HookHasEffect | HookPassive,
create,
undefined,
nextDeps,
);
}
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create, // 副作用函数
destroy, // 清理函数
deps, // 依赖数组
next: null,
};
// 添加到 Fiber 的 effect 链表
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
// Commit 阶段执行 effects
function commitPassiveEffects(finishedWork) {
commitPassiveUnmountEffects(finishedWork); // 执行清理
commitPassiveMountEffects(finishedWork); // 执行副作用
}
Hook 规则的实现
// React 在开发模式下检测 Hook 规则
let didWarnAboutUseEffectHookInLoop = false;
function useEffect(create, deps) {
if (__DEV__) {
// 检测是否在循环中调用
if (didWarnAboutUseEffectHookInLoop) {
console.error(
'Hooks 不能在循环、条件或嵌套函数中调用'
);
}
}
return mountEffect(create, deps);
}
// ESLint 插件检测
// eslint-plugin-react-hooks
{
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
总结
React Fiber 是一个精巧的架构设计,它通过:
- 链表数据结构 - 实现可中断的遍历
- 双缓存机制 - 提高复用性和性能
- 时间切片 - 避免长时间阻塞主线程
- 优先级调度 - 先处理紧急任务
- Lane 模型 - 更细粒度的优先级控制
最终实现了:
- ✅ 更流畅的用户体验
- ✅ 更好的性能
- ✅ 支持并发特性(Concurrent Mode)
- ✅ 为未来的新特性打下基础
理解 Fiber 的关键是理解它的分治思想:把大任务拆分成小任务,每个小任务可以被中断和恢复,通过优先级调度,先处理重要的任务,最终在用户无感知的情况下完成所有工作。