Skip to content

Fiber 架构与 Diff 算法

1. 为什么需要 Fiber?

1.1 Stack Reconciler 的问题 (React 15)

┌─────────────────────────────────────────────────────────────────────┐
│                    Stack Reconciler (React 15)                      │
├─────────────────────────────────────────────────────────────────────┤
│  render() → 递归遍历整棵树 → 同步更新 DOM                             │
│                                                                     │
│  组件树深度大时:                                                     │
│  ┌────────┬────────┬────────┬────────┬────────┐                     │
│  │ JS执行  │ JS执行  │ JS执行  │ JS执行  │ 渲染   │                    │
│  │ 16ms+  │ 16ms+  │ 16ms+  │ 16ms+  │       │                     │
│  └────────┴────────┴────────┴────────┴────────┘                     │
│            ↑                                                        │
│        掉帧、卡顿! (超过 16.6ms/帧)                                  │
└─────────────────────────────────────────────────────────────────────┘

1.2 Fiber Reconciler 的解决方案 (React 16+)

┌─────────────────────────────────────────────────────────────────────┐
│                    Fiber Reconciler (React 16+)                     │
├─────────────────────────────────────────────────────────────────────┤
│  Time Slicing (时间切片):                                           │
│  ┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐                   │
│  │Work││渲染││Work││渲染││Work││渲染││Work││渲染│                    │
│  │ 5ms││    ││ 5ms││    ││ 5ms││    ││ 5ms││    │                   │
│  └────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘                   │
│      ↑                                                              │
│  可中断/恢复, 每帧预留时间给浏览器渲染                                │
└─────────────────────────────────────────────────────────────────────┘
特性Stack ReconcilerFiber Reconciler
更新方式同步递归异步可中断
数据结构调用栈链表
优先级支持优先级调度
中断恢复不支持支持

2. Fiber 结构 🔥

2.1 什么是 Fiber?

Fiber 有三层含义:

  1. 架构角度: 新的 Reconciler 架构
  2. 数据结构: 虚拟 DOM 的增强版,也是工作单元
  3. 工作单元: 一个 Fiber 节点对应一次增量渲染任务

2.2 Fiber 节点结构

javascript
function FiberNode(tag, pendingProps, key, mode) {
    // ============ 静态数据 (描述组件) ============
    this.tag = tag;              // 组件类型 (FunctionComponent, ClassComponent, HostComponent...)
    this.key = key;              // key 属性
    this.elementType = null;     // 元素类型 (div, span, MyComponent)
    this.type = null;            // 组件函数/类
    this.stateNode = null;       // 对应的真实 DOM / 类实例

    // ============ Fiber 链表结构 ============
    this.return = null;          // 父 Fiber
    this.child = null;           // 第一个子 Fiber
    this.sibling = null;         // 下一个兄弟 Fiber
    this.index = 0;              // 在兄弟中的索引

    // ============ 工作单元数据 ============
    this.pendingProps = null;    // 新 props
    this.memoizedProps = null;   // 上次渲染的 props
    this.memoizedState = null;   // 上次渲染的 state (Hooks 链表头)
    this.updateQueue = null;     // 更新队列

    // ============ 副作用 ============
    this.flags = NoFlags;        // 副作用标记 (Placement, Update, Deletion...)
    this.subtreeFlags = NoFlags; // 子树副作用
    this.deletions = null;       // 待删除的子节点

    // ============ 优先级调度 ============
    this.lanes = NoLanes;        // 本节点优先级
    this.childLanes = NoLanes;   // 子树优先级

    // ============ 双缓冲 ============
    this.alternate = null;       // 连接 current 和 workInProgress
}

2.3 Fiber 链表结构图解

                    ┌────────────┐
                    │    App     │ (root)
                    └─────┬──────┘
                          │ child
                    ┌─────▼──────┐
      return ←──────│   Header   │
                    └─────┬──────┘
                          │ sibling
                    ┌─────▼──────┐
      return ←──────│   Content  │──────→ sibling
                    └─────┬──────┘         (Footer)
                          │ child
                    ┌─────▼──────┐
      return ←──────│   Title    │────→ sibling (List)
                    └────────────┘

2.4 关键字段详解

字段类型说明
tagnumberFiber 类型 (见 WorkTag)
typeFunction/Class/string组件函数、类或原生标签名
stateNodeDOM/Instance对应的 DOM 节点或类实例
memoizedStateanyFunction 组件的 Hooks 链表
flagsnumber副作用标记 (位运算)
alternateFiber双缓冲对应节点
lanesnumber优先级 (Lane 模型)

2.5 WorkTag 类型

javascript
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // 首次渲染前不确定
export const HostRoot = 3;               // 根节点
export const HostPortal = 4;             // Portal
export const HostComponent = 5;          // 原生 DOM (div, span)
export const HostText = 6;               // 文本节点
export const Fragment = 7;
export const ContextProvider = 10;
export const ContextConsumer = 11;
export const ForwardRef = 12;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const SuspenseComponent = 13;
// ...

2.6 Effect Flags (副作用标记)

javascript
export const NoFlags = 0b0000000000000000000;
export const Placement = 0b0000000000000000010;      // 插入
export const Update = 0b0000000000000000100;         // 更新
export const Deletion = 0b0000000000000001000;       // 删除
export const ChildDeletion = 0b0000000000000010000;  // 子节点删除
export const ContentReset = 0b0000000000000100000;   // 重置内容
export const Callback = 0b0000000000001000000;       // 回调
export const Ref = 0b0000000000010000000;            // Ref
export const Snapshot = 0b0000000000100000000;       // getSnapshotBeforeUpdate
export const Passive = 0b0000000001000000000;        // useEffect

// 组合标记
export const MutationMask = Placement | Update | ChildDeletion | ...;
export const LayoutMask = Update | Callback | Ref;
export const PassiveMask = Passive | ChildDeletion;

3. 双缓冲 (Double Buffering) 🔥

3.1 Current 树与 WorkInProgress 树

  ┌─────────────────────┐          ┌─────────────────────┐
  │    Current 树        │          │  WorkInProgress 树   │
  │   (当前屏幕显示)      │          │   (后台构建中)        │
  ├─────────────────────┤          ├─────────────────────┤
  │      FiberRoot      │◄────────►│      FiberRoot      │
  │         │           │ alternate│         │           │
  │    ┌────▼────┐      │          │    ┌────▼────┐      │
  │    │  App    │◄─────┼──────────┼────│  App    │      │
  │    └────┬────┘      │          │    └────┬────┘      │
  │         │           │          │         │           │
  │    ┌────▼────┐      │          │    ┌────▼────┐      │
  │    │ Child   │◄─────┼──────────┼────│ Child   │      │
  │    └─────────┘      │          │    └─────────┘      │
  └─────────────────────┘          └─────────────────────┘

3.2 工作流程

javascript
// 1. 首次渲染
// current = null, 创建 workInProgress 树

// 2. 首次渲染完成
// workInProgress 树变成 current 树

// 3. 更新时
// 基于 current 创建 workInProgress (通过 alternate 复用)
// workInProgress.alternate = current
// current.alternate = workInProgress

// 4. 更新完成
// workInProgress 替换 current
// root.current = finishedWork

3.3 为什么需要双缓冲?

  1. 增量渲染: 可以在后台构建新树,不阻塞当前显示
  2. 快速切换: 构建完成后直接切换指针
  3. 中断恢复: 中断时 current 树仍完整,可继续渲染

4. Render 阶段 (Reconciliation)

4.1 工作循环 (Work Loop)

javascript
function workLoopConcurrent() {
    // 有剩余时间且有工作
    while (workInProgress !== null && !shouldYield()) {
        performUnitOfWork(workInProgress);
    }
}

function workLoopSync() {
    // 同步模式不检查时间
    while (workInProgress !== null) {
        performUnitOfWork(workInProgress);
    }
}

4.2 performUnitOfWork

javascript
function performUnitOfWork(unitOfWork) {
    const current = unitOfWork.alternate;
    
    // 1. beginWork: 向下递归创建子 Fiber
    let next = beginWork(current, unitOfWork, renderLanes);
    
    unitOfWork.memoizedProps = unitOfWork.pendingProps;
    
    if (next === null) {
        // 2. 没有子节点,completeWork 向上归
        completeUnitOfWork(unitOfWork);
    } else {
        workInProgress = next;
    }
}

4.3 beginWork (递)

深度优先遍历 - "递" 阶段
                 
    App (beginWork)


   Header (beginWork) → Content (beginWork)
     │                       │
     ▼                       ▼
   Logo                    Title → List
     │                       │       │
     ▼                       ▼       ▼
   null                    null    Items...
javascript
function beginWork(current, workInProgress, renderLanes) {
    // 根据 Fiber.tag 执行不同逻辑
    switch (workInProgress.tag) {
        case FunctionComponent:
            return updateFunctionComponent(current, workInProgress, ...);
        case ClassComponent:
            return updateClassComponent(current, workInProgress, ...);
        case HostComponent:
            return updateHostComponent(current, workInProgress);
        // ...
    }
}

4.4 completeWork (归)

深度优先遍历 - "归" 阶段

   null → Logo (complete) → Header (complete)

                            App (complete)

   null → Title → List → Content (complete)
javascript
function completeWork(current, workInProgress, renderLanes) {
    switch (workInProgress.tag) {
        case HostComponent: {
            if (current !== null) {
                // 更新: 对比 props, 打 Update 标记
                updateHostComponent(current, workInProgress, ...);
            } else {
                // 首次渲染: 创建 DOM 节点
                const instance = createInstance(type, newProps, ...);
                appendAllChildren(instance, workInProgress, ...);
                workInProgress.stateNode = instance;
            }
            // 冒泡 flags
            bubbleProperties(workInProgress);
            return null;
        }
        // ...
    }
}

5. Commit 阶段

5.1 三个子阶段

Commit Phase (同步不可中断)

    ├─── BeforeMutation ─── getSnapshotBeforeUpdate

    ├─── Mutation ─────────  真实 DOM 操作 (增删改)
    │                        - commitPlacement
    │                        - commitUpdate
    │                        - commitDeletion

    └─── Layout ───────────  componentDidMount/Update
                             useLayoutEffect

5.2 Effect List (React 17-)

在 React 17 之前,使用 Effect List 链表收集有副作用的节点:

  firstEffect → Fiber1 → Fiber2 → Fiber3 → lastEffect
                  │         │         │
              Placement  Update   Deletion

5.3 SubtreeFlags (React 18+)

javascript
// React 18 使用 subtreeFlags 标记子树是否有副作用
function bubbleProperties(completedWork) {
    let subtreeFlags = NoFlags;
    let child = completedWork.child;
    
    while (child !== null) {
        subtreeFlags |= child.subtreeFlags;
        subtreeFlags |= child.flags;
        child = child.sibling;
    }
    
    completedWork.subtreeFlags |= subtreeFlags;
}

6. Diff 算法 🔥

6.1 三个限制策略

┌─────────────────────────────────────────────────────────────────────┐
│                       React Diff 策略                                │
├─────────────────────────────────────────────────────────────────────┤
│ 1. 同层级比较: 只对比同一层级,不跨层级移动                           │
│                                                                     │
│ 2. 类型检测: 类型不同直接销毁重建                                    │
│    <div> → <span>  ❌ 销毁 div 及其子树,创建 span                  │
│                                                                     │
│ 3. Key 优化: 相同 key 认为是同一节点,可复用                          │
└─────────────────────────────────────────────────────────────────────┘

6.2 单节点 Diff

javascript
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
    const key = element.key;
    let child = currentFirstChild;
    
    while (child !== null) {
        // key 相同
        if (child.key === key) {
            // type 相同 → 复用
            if (child.elementType === element.type) {
                deleteRemainingChildren(returnFiber, child.sibling);
                const existing = useFiber(child, element.props);
                existing.return = returnFiber;
                return existing;
            }
            // type 不同 → 删除所有旧节点
            deleteRemainingChildren(returnFiber, child);
            break;
        } else {
            // key 不同 → 删除该节点
            deleteChild(returnFiber, child);
        }
        child = child.sibling;
    }
    
    // 创建新节点
    const created = createFiberFromElement(element);
    created.return = returnFiber;
    return created;
}

6.3 多节点 Diff (reconcileChildrenArray)

React 多节点 Diff 分两轮遍历:

第一轮: 处理更新的节点

javascript
// 同时遍历 newChildren 和 oldFiber
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
    // key 不同,跳出第一轮
    if (oldFiber.key !== newChildren[newIdx].key) {
        break;
    }
    // key 相同,尝试复用
    const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]);
    if (newFiber === null) break;  // type 不同
    
    // 复用成功
    oldFiber = oldFiber.sibling;
}

第二轮: 处理剩余节点

javascript
// 情况1: newChildren 遍历完,删除剩余 oldFiber
if (newIdx === newChildren.length) {
    deleteRemainingChildren(returnFiber, oldFiber);
    return resultingFirstChild;
}

// 情况2: oldFiber 遍历完,插入剩余 newChildren
if (oldFiber === null) {
    for (; newIdx < newChildren.length; newIdx++) {
        const newFiber = createChild(returnFiber, newChildren[newIdx]);
        // 插入...
    }
    return resultingFirstChild;
}

// 情况3: 都有剩余,建立 Map 查找可复用节点
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

for (; newIdx < newChildren.length; newIdx++) {
    const newFiber = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx]);
    if (newFiber !== null) {
        // 判断是否需要移动
        if (newFiber.alternate !== null) {
            existingChildren.delete(newFiber.key ?? newIdx);
        }
        placeChild(newFiber, lastPlacedIndex, newIdx);
    }
}

6.4 移动判断 (仅右移策略)

javascript
function placeChild(newFiber, lastPlacedIndex, newIndex) {
    newFiber.index = newIndex;
    const current = newFiber.alternate;
    
    if (current !== null) {
        const oldIndex = current.index;
        if (oldIndex < lastPlacedIndex) {
            // 移动: 标记 Placement
            newFiber.flags |= Placement;
            return lastPlacedIndex;
        } else {
            // 不移动
            return oldIndex;
        }
    } else {
        // 新节点: 需要插入
        newFiber.flags |= Placement;
        return lastPlacedIndex;
    }
}

示例

旧: A(0) B(1) C(2) D(3)
新: A     C     D     B

lastPlacedIndex = 0

A: oldIndex=0 >= lastPlacedIndex(0), 不移动, lastPlacedIndex = 0
C: oldIndex=2 >= lastPlacedIndex(0), 不移动, lastPlacedIndex = 2
D: oldIndex=3 >= lastPlacedIndex(2), 不移动, lastPlacedIndex = 3
B: oldIndex=1 < lastPlacedIndex(3), 移动!

结果: 只有 B 需要移动到末尾

WARNING

这就是为什么将节点从末尾移到开头效率很低 —— 其余所有节点都会被标记为移动。


7. 优先级与 Lanes

7.1 Lane 模型

javascript
export const NoLanes = 0b0000000000000000000000000000000;
export const NoLane = 0b0000000000000000000000000000000;

export const SyncLane = 0b0000000000000000000000000000001;            // 同步
export const InputContinuousLane = 0b0000000000000000000000000000100; // 连续输入
export const DefaultLane = 0b0000000000000000000000000010000;         // 默认
export const TransitionLane1 = 0b0000000000000000000000001000000;     // Transition
// ...
export const IdleLane = 0b0100000000000000000000000000000;            // 空闲
export const OffscreenLane = 0b1000000000000000000000000000000;       // 离屏

7.2 优先级场景

优先级场景Lane
最高flushSync, 离散事件 (click)SyncLane
较高连续输入 (scroll, drag)InputContinuousLane
默认普通更新DefaultLane
较低startTransitionTransitionLane
最低requestIdleCallbackIdleLane

8. 面试高频问题

Q1: Fiber 是什么?

Fiber 有三层含义:

  1. 架构: React 16 的新 Reconciler
  2. 数据结构: 虚拟 DOM 增强版,链表结构
  3. 工作单元: 一个 Fiber 对应一次增量任务

Q2: 为什么用链表不用树?

  • 可中断: 链表可以记录遍历位置,随时恢复
  • 扁平化: 树的递归变成循环迭代
  • 三指针: child/sibling/return 完整描述关系

Q3: Render 阶段和 Commit 阶段的区别?

阶段RenderCommit
可中断✅ 可❌ 不可
副作用无 (纯计算)有 (DOM 操作)
输出Fiber 树 + Effect 标记真实 DOM

Q4: React Diff 的时间复杂度?

O(n) — 通过三条策略将 O(n³) 优化为 O(n)

Q5: 为什么 key 用 index 不好?

javascript
// 错误示例: 删除第一个元素
items = ['A', 'B', 'C'] → ['B', 'C']
// key=[0,1,2] → key=[0,1]
// React 认为: 0→0复用, 1→1复用, 删除2
// 实际效果: A变B, B变C, 删除C (错误!)

Q6: 双缓冲的作用?

  1. 后台构建新树,不阻塞当前显示
  2. 构建完成快速切换
  3. 中断时 current 树保持完整

Q7: React 18 的 Concurrent 特性基于什么实现?

基于 Fiber 架构的可中断渲染 + Lane 优先级模型 + Scheduler 调度器

前端面试知识库