React Fiber 深度解析

25 阅读24分钟

React Fiber 深度解析

目录

  1. 什么是 React Fiber - 具像化理解
  2. Fiber 架构原理详解
  3. 源码深度剖析
  4. 面试高频问题与最优答案

什么是 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 的三重含义

  1. 作为架构 - 一种全新的协调引擎
  2. 作为数据结构 - 组件的工作单元
  3. 作为执行单元 - 可暂停、可恢复的计算单位

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. 首次渲染: 创建 WorkInProgress2. 完成后: 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 解决方案:

  1. 时间切片(Time Slicing)

    • 将长任务拆分成多个小任务
    • 每个任务执行约 5ms
    • 执行完一个任务检查是否有更高优先级任务
    • 有则暂停当前任务,执行高优先级任务
  2. 可中断的更新

    • Fiber 是一个链表结构,可以暂停和恢复
    • 通过 workInProgress 指针记录当前位置
    • 可以随时中断,去处理更紧急的事情
  3. 优先级调度

    • 不同更新有不同优先级
    • 用户交互(点击、输入)优先级最高
    • 网络请求、动画次之
    • 后台任务优先级最低

核心数据结构:

{
  // 链表结构
  return: Fiber,    // 父节点
  child: Fiber,     // 第一个子节点
  sibling: Fiber,   // 下一个兄弟节点
  
  // 双缓存
  alternate: Fiber, // 另一棵树的对应节点
  
  // 副作用
  flags: Flags,     // 增删改标记
  
  // 优先级
  lanes: Lanes,     // 任务优先级
}

Q2: Fiber 的工作原理是什么?详细说说 Render 和 Commit 阶段

最优答案:

Fiber 的工作分为两个主要阶段:

Render 阶段(可中断,纯净无副作用)

目的:找出需要变化的节点,打上标记

过程:

  1. 递阶段(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;
    }
    
  2. 归阶段(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

三个子阶段:

  1. Before Mutation(DOM 操作前)

    function commitBeforeMutationEffects() {
      // 1. 调用 getSnapshotBeforeUpdate
      // 2. 异步调度 useEffect
    }
    
  2. 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;
      }
    }
    
  3. Layout(DOM 操作后)

    function commitLayoutEffects() {
      // 1. 调用 componentDidMount/Update
      // 2. 调用 useLayoutEffect
      // 3. 更新 ref
    }
    

为什么 Commit 不可中断?

  • DOM 操作必须原子性完成
  • 避免用户看到中间状态
  • 保证生命周期顺序正确

Q3: Fiber 的 Diff 算法有什么优化?

最优答案:

React Fiber 的 Diff 算法基于三个假设,进行了针对性优化:

三大策略
  1. Tree Diff(树层级)

    • 只比较同层级节点
    • 不同层级的节点,直接删除重建
    • 时间复杂度:O(n)
  2. Component Diff(组件级别)

    • 同类型组件:继续比较
    • 不同类型:直接替换
    • shouldComponentUpdate 优化
  3. 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  │
       └───┬───┘                    └───┬───┘
           │                            │
       子 FiberFiber
工作流程
  1. 首次渲染

    // 1. 创建 FiberRoot
    const root = createFiberRoot(container);
    
    // 2. 创建 rootFiber (workInProgress)
    const uninitializedFiber = createHostRootFiber();
    root.current = uninitializedFiber;
    
    // 3. 构建 workInProgress 树
    // 4. 完成后,将 workInProgress 变成 current
    root.current = finishedWork;
    
  2. 更新阶段

    // 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;
    
为什么需要双缓存?
  1. 避免中间状态

    • Render 阶段可中断,可能被打断多次
    • 如果直接修改 current 树,用户会看到不完整的 UI
    • 在内存中构建完整的树,一次性切换
  2. 复用节点

    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;
    }
    
  3. 性能优化

    • 不需要每次都创建新对象
    • 两棵树来回切换,减少内存开销
类比理解

就像视频播放:

  • 单缓冲:直接在屏幕上绘制 → 会看到绘制过程(闪烁)
  • 双缓冲:先在后台绘制完整帧 → 一次性显示(流畅)

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 的优势
  1. 更细粒度的优先级

    // 可以表示 31 种不同的优先级
    // 每个 bit 是一个独立的"车道"
    
  2. 可以组合

    // 位运算合并多个优先级
    const lanes = SyncLane | InputContinuousLane;
    // 0b0000000000000000000000000000101
    
    // 检查是否包含某个优先级
    if (lanes & SyncLane) {
      // 包含同步任务
    }
    
    // 移除某个优先级
    const remaining = lanes & ~SyncLane;
    
  3. 批量处理

    // 多个相同优先级的更新可以批量处理
    const updateLanes = TransitionLane1 | TransitionLane2;
    
    // 一次性处理所有 Transition 更新
    if (updateLanes & TransitionLanes) {
      processTransitionUpdates();
    }
    
  4. 更好的性能

    // 位运算比数值比较更快
    
    // 找到最高优先级
    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;
  }
}
对比总结
特性expirationTimeLane
实现方式时间戳二进制位
优先级数量有限31 种
可组合性
性能数值比较位运算(更快)
批处理困难容易
灵活性

Q6: Fiber 如何实现可中断的渲染?requestIdleCallback 的问题是什么?

最优答案:

可中断渲染的核心机制
  1. 时间切片(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;
    }
    
  2. 工作单元拆分

    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;
      }
    }
    
  3. 保存中断状态

    // 通过链表结构保存进度
    let workInProgress = null; // 当前处理的 Fiber
    let workInProgressRoot = null; // 当前根节点
    let workInProgressRootRenderLanes = NoLanes; // 当前优先级
    
    // 中断时,这些变量保存了当前状态
    // 恢复时,从 workInProgress 继续
    
requestIdleCallback 的问题

React 最初想使用 requestIdleCallback,但发现存在问题:

  1. 兼容性差

    // Safari 不支持
    if (typeof requestIdleCallback === 'function') {
      // 支持
    } else {
      // 不支持,需要 polyfill
    }
    
  2. 执行频率不稳定

    // 浏览器空闲时才执行
    requestIdleCallback((deadline) => {
      while (deadline.timeRemaining() > 0) {
        // 执行任务
      }
    });
    
    // 问题: 如果浏览器一直忙,任务可能永远不执行
    // 用户交互密集时,空闲时间很少
    
  3. 优先级不受控

    // requestIdleCallback 只有一个优先级
    // 无法区分紧急任务和普通任务
    requestIdleCallback(lowPriorityTask);
    
    // 需要更细粒度的优先级控制
    
  4. 帧率限制

    // 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 派生 staterender()                               // 渲染React 更新 DOM 和 refs
  ↓
componentDidMount()                    // 挂载完成

// 2. 更新阶段
New props / setState() / forceUpdate()
  ↓
static getDerivedStateFromProps()      // 从 props 派生 stateshouldComponentUpdate()                // 是否需要更新?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 是一个精巧的架构设计,它通过:

  1. 链表数据结构 - 实现可中断的遍历
  2. 双缓存机制 - 提高复用性和性能
  3. 时间切片 - 避免长时间阻塞主线程
  4. 优先级调度 - 先处理紧急任务
  5. Lane 模型 - 更细粒度的优先级控制

最终实现了:

  • ✅ 更流畅的用户体验
  • ✅ 更好的性能
  • ✅ 支持并发特性(Concurrent Mode)
  • ✅ 为未来的新特性打下基础

理解 Fiber 的关键是理解它的分治思想:把大任务拆分成小任务,每个小任务可以被中断和恢复,通过优先级调度,先处理重要的任务,最终在用户无感知的情况下完成所有工作。