今天我们来看看,React触发了setState后,执行逻辑和首次执行有什么不一样。下面会以一个示例为切入口来讲解整个过程,重点会分析setState触发时的优先级机制以及React做了的一些优化点。

目录

  1. 示例项目场景
  2. setState 调用瞬间发生了什么
  3. Update 对象的创建与入队
  4. 调度更新的触发
  5. 优先级计算与Lane模型
  6. Render阶段的完整流程
  7. Render阶段性能优化:Lane标记与Bailout机制
  8. Commit阶段的完整流程
  9. 批量更新机制
  10. 完整时序图

1. 示例项目场景

1.1 项目背景

我们以一个电商商品列表页为例:

// ProductList.jsx - 商品列表组件
import React, { useState, useEffect } from 'react';

function ProductList() {
  const [products, setProducts] = useState([]);
  const [filterCategory, setFilterCategory] = useState('all');
  const [sortBy, setSortBy] = useState('price');
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    fetchProducts();
  }, [filterCategory, sortBy]);

  const fetchProducts = async () => {
    setLoading(true);
    const data = await api.getProducts({ category: filterCategory, sort: sortBy });
    setProducts(data);
    setLoading(false);
  };

  const handleCategoryChange = (category) => {
    // 重点:这里调用了 setState
    setFilterCategory(category);  // ← 我们要分析这个调用
  };

  return (
    <div>
      <CategoryFilter
        selected={filterCategory}
        onChange={handleCategoryChange}
      />
      <SortOptions selected={sortBy} onChange={setSortBy} />
      {loading ? (
        <LoadingSpinner />
      ) : (
        <ProductGrid products={products} />
      )}
    </div>
  );
}

1.2 组件树结构

App
 ├─ Header
 ├─ ProductList (我们关注的组件)
 │   ├─ CategoryFilter
 │   │   └─ FilterButton (点击这里触发 setState)
 │   ├─ SortOptions
 │   └─ ProductGrid
 │       ├─ ProductCard
 │       ├─ ProductCard
 │       └─ ProductCard
 └─ Footer

1.3 触发场景

用户操作:点击"电子产品"分类按钮

// FilterButton.jsx
function FilterButton({ category, isSelected, onClick }) {
  return (
    <button
      className={isSelected ? 'active' : ''}
      onClick={() => onClick(category)}  // ← 用户点击
    >
      {category}
    </button>
  );
}

// 用户点击 → onClick('electronics') → handleCategoryChange('electronics')
// → setFilterCategory('electronics') ← 从这里开始分析

2. setState 调用瞬间发生了什么

2.1 代码执行路径

// 用户点击按钮
onClick('electronics')// 调用父组件的回调
handleCategoryChange('electronics')// 调用 setState
setFilterCategory('electronics')

setFilterCategory 是什么?

const [filterCategory, setFilterCategory] = useState('all');
//                     ↑
//                     这是 dispatchSetState 函数(通过 bind 绑定了参数)

2.2 dispatchSetState 函数分析

文件位置packages/react-reconciler/src/ReactFiberHooks.js:3599

// setFilterCategory('electronics') 实际调用:
function dispatchSetState<S, A>(
  fiber: Fiber,              // ProductList 组件的 Fiber 节点(已绑定)
  queue: UpdateQueue<S, A>,  // filterCategory 的更新队列(已绑定)
  action: A,                 // 'electronics'(用户传入)
): void {

  // ========== 步骤1:参数验证 ==========
  if (__DEV__) {
    // 检查是否传入了第二个参数(旧版API)
    const args = arguments;
    if (typeof args[3] === 'function') {
      console.error(
        "State updates don't support the second callback argument. " +
        "Use useEffect() instead."
      );
    }
  }

  // ========== 步骤2:计算更新优先级 ==========
  const lane = requestUpdateLane(fiber);
  // lane 的值可能是:
  // - SyncLane (同步优先级,如 flushSync 或 Legacy 模式)
  // - DefaultLane (默认优先级,如用户交互)
  // - TransitionLane (过渡优先级,如 startTransition)

  // ========== 步骤3:调用内部实现 ==========
  const didScheduleUpdate = dispatchSetStateInternal(
    fiber,
    queue,
    action,    // 'electronics'
    lane,
  );

  // ========== 步骤4:性能追踪(DEV) ==========
  if (didScheduleUpdate) {
    startUpdateTimerByLane(lane, 'setState()', fiber);
  }

  // ========== 步骤5:DevTools 标记 ==========
  markUpdateInDevTools(fiber, lane, action);
}

此时的关键变量

fiber = {
  tag: FunctionComponent,          // 类型:函数组件
  type: ProductList,               // 组件函数
  stateNode: null,                 // 函数组件没有实例
  memoizedState: Hook链表,         // 指向第一个 Hook
  updateQueue: null,               // 函数组件的 updateQueue 在 Hook 上
  // ...
}

queue = {
  pending: null,                   // 待处理的更新(环形链表)
  lanes: NoLanes,                  // 当前队列的优先级
  dispatch: setFilterCategory,     // dispatch 函数(就是自己)
  lastRenderedReducer: basicStateReducer,  // 状态计算函数
  lastRenderedState: 'all',        // 上次渲染的值
}

action = 'electronics'             // 新的状态值
lane = DefaultLane                 // 假设是用户交互,使用默认优先级

2.3 requestUpdateLane:计算优先级

文件位置packages/react-reconciler/src/ReactFiberWorkLoop.js

export function requestUpdateLane(fiber: Fiber): Lane {

  // 步骤1:检查是否在 transition 中
  const transition = requestCurrentTransition();
  if (transition !== null) {
    // 在 startTransition 中调用,使用过渡优先级
    return requestTransitionLane(transition);
  }

  // 步骤2:获取当前的更新优先级
  const updateLane = getCurrentUpdatePriority();
  if (updateLane !== NoLane) {
    return updateLane;
  }

  // 步骤3:根据事件优先级计算
  const eventLane = getCurrentEventPriority();
  return eventLane;

  // 对于我们的案例:
  // - 用户点击事件 → DiscreteEventPriority → SyncLane
  // - 但在并发模式下可能是 DefaultLane
}

优先级映射表

事件类型 事件优先级 Lane 示例
用户交互 DiscreteEventPriority SyncLane / InputContinuousLane 点击、输入
连续事件 ContinuousEventPriority DefaultLane 滚动、鼠标移动
默认 DefaultEventPriority DefaultLane setTimeout
空闲 IdleEventPriority IdleLane requestIdleCallback

3. Update 对象的创建与入队

3.1 dispatchSetStateInternal 详解

文件位置packages/react-reconciler/src/ReactFiberHooks.js:3629

function dispatchSetStateInternal<S, A>(
  fiber: Fiber,              // ProductList 的 Fiber
  queue: UpdateQueue<S, A>,  // filterCategory 的队列
  action: A,                 // 'electronics'
  lane: Lane,                // DefaultLane
): boolean {

  // ========== 步骤1:创建 Update 对象 ==========
  const update: Update<S, A> = {
    lane,                    // DefaultLane
    revertLane: NoLane,      // 用于 Suspense 回退
    gesture: null,           // 手势相关(实验性)
    action,                  // 'electronics'
    hasEagerState: false,    // 是否已预计算
    eagerState: null,        // 预计算结果
    next: (null: any),       // 下一个 Update(环形链表)
  };

  console.log('创建 Update 对象:', update);

  // ========== 步骤2:检查是否在渲染阶段 ==========
  if (isRenderPhaseUpdate(fiber)) {
    // 在组件渲染过程中调用了 setState
    // 例如:在 render 函数体中直接调用 setState
    enqueueRenderPhaseUpdate(queue, update);
    return false;  // 不调度新的更新
  }

  // ========== 步骤3:尝试"预计算优化" ==========
  const alternate = fiber.alternate;  // current 树的对应节点

  // 条件:当前没有待处理的更新
  if (
    fiber.lanes === NoLanes &&
    (alternate === null || alternate.lanes === NoLanes)
  ) {
    // 可以尝试预先计算新状态
    const lastRenderedReducer = queue.lastRenderedReducer;

    if (lastRenderedReducer !== null) {
      try {
        const currentState: S = (queue.lastRenderedState: any);  // 'all'

        // 计算新状态
        const eagerState = lastRenderedReducer(currentState, action);
        // basicStateReducer('all', 'electronics') → 'electronics'

        console.log('预计算新状态:', eagerState);

        // 保存预计算结果
        update.hasEagerState = true;
        update.eagerState = eagerState;  // 'electronics'

        // 如果新旧状态相同,可以跳过更新
        if (is(eagerState, currentState)) {
          // 'electronics' === 'all' → false
          // 不相同,需要继续更新
        } else {
          // 我们的案例:状态不同,继续执行
          console.log('状态发生变化,需要更新');
        }
      } catch (error) {
        // 计算出错,继续正常流程
      }
    }
  }

  // ========== 步骤4:将 Update 加入并发更新队列 ==========
  const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);

  console.log('Update 已加入队列');
  console.log('FiberRoot:', root);

  // ========== 步骤5:调度更新 ==========
  if (root !== null) {
    // 触发 Fiber 树的重新渲染
    scheduleUpdateOnFiber(root, fiber, lane);

    // 处理 transition 相关逻辑
    entangleTransitionUpdate(root, queue, lane);

    return true;  // 已调度更新
  }

  return false;  // 未调度更新
}

执行后的状态

update = {
  lane: DefaultLane,
  revertLane: NoLane,
  gesture: null,
  action: 'electronics',       // ← 新值
  hasEagerState: true,         // ← 已预计算
  eagerState: 'electronics',   // ← 预计算结果
  next: null,
}

3.2 enqueueConcurrentHookUpdate:入队逻辑

文件位置packages/react-reconciler/src/ReactFiberConcurrentUpdates.js

export function enqueueConcurrentHookUpdate<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  update: Update<S, A>,
  lane: Lane,
): FiberRoot | null {

  // 步骤1:获取 Hook 更新队列
  const concurrentQueue: ConcurrentQueue = (queue: any);
  const concurrentUpdate: ConcurrentUpdate = (update: any);

  // 步骤2:将 Update 加入内部队列
  enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);

  // 步骤3:返回 FiberRoot
  return getRootForUpdatedFiber(fiber);
}

function enqueueUpdate(
  fiber: Fiber,
  queue: ConcurrentQueue | null,
  update: ConcurrentUpdate | null,
  lane: Lane,
) {
  // 将 Update 添加到 queue.pending(环形链表)

  // 第一次添加
  if (queue.pending === null) {
    // 创建环形链表:update.next = update(指向自己)
    update.next = update;
  } else {
    // 插入到环形链表
    update.next = queue.pending.next;
    queue.pending.next = update;
  }

  // pending 始终指向最后一个 Update
  queue.pending = update;
}

环形链表结构

第一次调用 setState('electronics'):
  queue.pending → Update1
                    ↓
                  next → Update1 (指向自己)

第二次调用 setState('books'):
  queue.pending → Update2
                    ↓
                  next → Update1
                           ↓
                         next → Update2 (形成环)

4. 调度更新的触发(和首次更新大部分差不多)

4.1 scheduleUpdateOnFiber:调度入口

文件位置packages/react-reconciler/src/ReactFiberWorkLoop.js

export function scheduleUpdateOnFiber(
  root: FiberRoot,   // 应用的根节点
  fiber: Fiber,      // ProductList 的 Fiber
  lane: Lane,        // DefaultLane
): void {

  // ========== 步骤1:检查无限循环 ==========
  if (__DEV__) {
    // 防止在 render 中无限调用 setState
    if (
      nestedUpdateCount > NESTED_UPDATE_LIMIT &&
      root === rootWithNestedUpdates
    ) {
      throw new Error(
        'Maximum update depth exceeded. This can happen when a ' +
        'component repeatedly calls setState inside componentWillUpdate ' +
        'or componentDidUpdate.'
      );
    }
  }

  // ========== 步骤2:标记根节点有更新 ==========
  markRootUpdated(root, lane);

  console.log('标记 FiberRoot 有待处理的更新');
  console.log('Lane:', lane);

  // ========== 步骤3:检查是否在渲染中 ==========
  if (
    (executionContext & RenderContext) !== NoContext &&
    root === workInProgressRoot
  ) {
    // 正在渲染当前树,标记工作中的 Fiber
    workInProgressRootDidSkipSuspendedSiblings = true;
  }

  // ========== 步骤4:确保根节点被调度 ==========
  ensureRootIsScheduled(root);

  console.log('调度完成');
}

4.2 markRootUpdated:标记更新

function markRootUpdated(root: FiberRoot, updateLane: Lane) {
  // 将新的 lane 添加到 root.pendingLanes
  root.pendingLanes |= updateLane;

  // 如果不是过渡 lane,清除 suspended lanes
  if (updateLane !== IdleLane) {
    root.suspendedLanes = NoLanes;
    root.pingedLanes = NoLanes;
  }

  // 标记根节点有未提交的更新
  root.entangledLanes |= updateLane;

  console.log('root.pendingLanes:', lanesToString(root.pendingLanes));
}

FiberRoot 的状态变化

// 调用 setState 前
root.pendingLanes = NoLanes (0b0000000000000000000000000000000)

// 调用 setState 后
root.pendingLanes = DefaultLane (0b0000000000000000000000000000100)

4.3 ensureRootIsScheduled:核心调度逻辑

文件位置packages/react-reconciler/src/ReactFiberWorkLoop.js

function ensureRootIsScheduled(root: FiberRoot): void {

  // ========== 步骤1:标记饥饿的 lanes ==========
  const currentTime = now();
  markStarvedLanesAsExpired(root, currentTime);

  // 将等待太久的低优先级任务提升为高优先级

  // ========== 步骤2:获取下一个要处理的 lanes ==========
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
  );

  console.log('下一个要处理的 lanes:', lanesToString(nextLanes));

  // ========== 步骤3:如果没有待处理的工作 ==========
  if (nextLanes === NoLanes) {
    // 没有更新需要处理
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  }

  // ========== 步骤4:获取最高优先级 ==========
  const newCallbackPriority = getHighestPriorityLane(nextLanes);
  const existingCallbackPriority = root.callbackPriority;

  console.log('新的优先级:', newCallbackPriority);
  console.log('现有优先级:', existingCallbackPriority);

  // ========== 步骤5:检查是否可以复用现有调度 ==========
  if (
    existingCallbackPriority === newCallbackPriority &&
    // 特殊情况处理...
  ) {
    // 优先级相同,复用现有调度
    console.log('复用现有调度');
    return;
  }

  // ========== 步骤6:取消旧的调度 ==========
  if (existingCallbackNode !== null) {
    cancelCallback(existingCallbackNode);
  }

  // ========== 步骤7:调度新任务 ==========
  let newCallbackNode;

  if (includesSyncLane(newCallbackPriority)) {
    // 同步优先级(Legacy 模式或 flushSync)
    console.log('使用同步调度');

    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));

    // 立即刷新同步队列
    if (supportsMicrotasks) {
      scheduleMicrotask(flushSyncWorkOnRootsScheduledDuringFlush);
    } else {
      scheduleCallback(ImmediatePriority, flushSyncWorkOnRootsScheduledDuringFlush);
    }

    newCallbackNode = null;
  } else {
    // 并发模式
    console.log('使用并发调度');

    // 将 Lane 转换为 Scheduler 优先级
    const schedulerPriorityLevel = lanesToSchedulerPriority(newCallbackPriority);

    console.log('Scheduler 优先级:', schedulerPriorityLevel);

    // 调度并发任务
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root)
    );
  }

  // ========== 步骤8:保存调度信息 ==========
  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;

  console.log('调度已安排,等待执行');
}

Lane 到 Scheduler 优先级的映射

function lanesToSchedulerPriority(lanes: Lanes): SchedulerPriority {
  const lane = getHighestPriorityLane(lanes);

  if ((lane & SyncLane) !== NoLane) {
    return ImmediatePriority;     // 99
  }
  if ((lane & InputContinuousLane) !== NoLane) {
    return UserBlockingPriority;  // 98
  }
  if ((lane & DefaultLane) !== NoLane) {
    return NormalPriority;        // 97
  }
  if ((lane & IdleLane) !== NoLane) {
    return IdlePriority;          // 95
  }
  return NormalPriority;
}

5. 优先级计算与Lane模型

5.1 什么是 Lane

Lane 是 React 18 引入的优先级模型,使用 31 位二进制数 表示不同的优先级。

// packages/react-reconciler/src/ReactFiberLane.js

export const NoLane: Lane =                       0b0000000000000000000000000000000;
export const NoLanes: Lanes =                     0b0000000000000000000000000000000;

export const SyncLane: Lane =                     0b0000000000000000000000000000010;
export const SyncHydrationLane: Lane =            0b0000000000000000000000000000001;

export const InputContinuousLane: Lane =          0b0000000000000000000000000001000;
export const InputContinuousHydrationLane: Lane = 0b0000000000000000000000000000100;

export const DefaultLane: Lane =                  0b0000000000000000000000000100000;
export const DefaultHydrationLane: Lane =         0b0000000000000000000000000010000;

// 过渡优先级(共 15 个)
export const TransitionLane1: Lane =              0b0000000000000000000000001000000;
export const TransitionLane2: Lane =              0b0000000000000000000000010000000;
// ... TransitionLane3 - TransitionLane15

// 其他
export const RetryLane1: Lane =                   0b0000000001000000000000000000000;
export const IdleLane: Lane =                     0b0100000000000000000000000000000;
export const OffscreenLane: Lane =                0b1000000000000000000000000000000;

5.2 Lane 的优势

相比旧的 expirationTime 模型

特性 expirationTime Lane
表示方式 数字(时间戳) 二进制位掩码
优先级数量 有限 31 个独立优先级
批处理 困难 简单(位运算)
饥饿问题 需要特殊处理 自然支持
优先级插队 复杂 简单(清除位)

Lane 的操作

// 添加 lane
lanes |= newLane;  // 0b0100 | 0b0010 = 0b0110

// 移除 lane
lanes &= ~removedLane;  // 0b0110 & ~0b0010 = 0b0100

// 检查是否包含
(lanes & targetLane) !== NoLanes;

// 获取最高优先级 lane
function getHighestPriorityLane(lanes: Lanes): Lane {
  return lanes & -lanes;  // 位运算技巧:获取最右边的 1
  // 例如:0b0110 & -0b0110 = 0b0010
}

5.3 getNextLanes:选择下一个处理的 Lane

文件位置packages/react-reconciler/src/ReactFiberLane.js

export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {

  // 步骤1:获取所有待处理的 lanes
  const pendingLanes = root.pendingLanes;
  if (pendingLanes === NoLanes) {
    return NoLanes;  // 没有待处理的更新
  }

  let nextLanes = NoLanes;

  // 步骤2:获取非空闲的 lanes
  const nonIdlePendingLanes = pendingLanes & NonIdleLanes;

  if (nonIdlePendingLanes !== NoLanes) {
    // 步骤3:排除被阻塞的 lanes
    const nonIdleUnblockedLanes = nonIdlePendingLanes & ~root.suspendedLanes;

    if (nonIdleUnblockedLanes !== NoLanes) {
      // 步骤4:选择最高优先级
      nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
    } else {
      // 所有非空闲 lanes 都被阻塞,选择被 ping 的 lanes
      const nonIdlePingedLanes = nonIdlePendingLanes & root.pingedLanes;
      if (nonIdlePingedLanes !== NoLanes) {
        nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
      }
    }
  } else {
    // 只有空闲 lanes
    const unblockedLanes = pendingLanes & ~root.suspendedLanes;
    if (unblockedLanes !== NoLanes) {
      nextLanes = getHighestPriorityLanes(unblockedLanes);
    } else if (pingedLanes !== NoLanes) {
      nextLanes = getHighestPriorityLanes(pingedLanes);
    }
  }

  // 步骤5:如果没有选中任何 lane,返回 NoLanes
  if (nextLanes === NoLanes) {
    return NoLanes;
  }

  // 步骤6:处理纠缠的 lanes(entangled lanes)
  // 某些 lanes 必须一起处理
  nextLanes = mergeLanes(nextLanes, root.entangledLanes & nextLanes);

  return nextLanes;
}

6. Render阶段的完整流程

6.1 performConcurrentWorkOnRoot:并发工作入口

文件位置packages/react-reconciler/src/ReactFiberWorkLoop.js

function performConcurrentWorkOnRoot(
  root: FiberRoot,
  didTimeout: boolean,  // 是否超时
): RenderTaskFn | null {

  console.log('========== 开始并发渲染 ==========');

  // ========== 步骤1:准备渲染 ==========
  const originalCallbackNode = root.callbackNode;

  // 执行可能的被动效果(useEffect)
  const didFlushPassiveEffects = flushPassiveEffects();
  if (didFlushPassiveEffects) {
    // 可能产生了新的更新
    if (root.callbackNode !== originalCallbackNode) {
      // 调度被更改,退出
      return null;
    }
  }

  // ========== 步骤2:获取要渲染的 lanes ==========
  const lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
  );

  if (lanes === NoLanes) {
    // 没有工作要做
    return null;
  }

  console.log('渲染 lanes:', lanesToString(lanes));

  // ========== 步骤3:判断是否使用并发模式 ==========
  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) &&  // 不包含阻塞 lane
    !includesExpiredLane(root, lanes) &&   // 不包含过期 lane
    !didTimeout;                           // 没有超时

  console.log('是否使用时间切片:', shouldTimeSlice);

  // ========== 步骤4:执行渲染 ==========
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)  // 可中断渲染
    : renderRootSync(root, lanes);       // 同步渲染

  console.log('渲染退出状态:', exitStatus);

  // ========== 步骤5:处理渲染结果 ==========
  if (exitStatus !== RootInProgress) {
    // 渲染完成或出错

    if (exitStatus === RootDidNotComplete) {
      // 渲染未完成(被中断)
      markRootSuspended(root, lanes, NoLane, false);
    }

    if (exitStatus === RootFatalErrored) {
      // 致命错误
      prepareFreshStack(root, NoLanes);
      markRootSuspended(root, lanes, NoLane, false);
      ensureRootIsScheduled(root);
      return null;
    }

    if (exitStatus === RootErrored) {
      // 可恢复的错误,重试
      // ...
    }

    // ========== 步骤6:检查是否完成 ==========
    if (exitStatus === RootCompleted) {
      console.log('渲染完成,准备提交');

      const finishedWork: Fiber = (root.current.alternate: any);
      root.finishedWork = finishedWork;
      root.finishedLanes = lanes;

      // ========== 步骤7:提交更新 ==========
      finishConcurrentRender(root, exitStatus, finishedWork, lanes);
    }
  }

  // ========== 步骤8:确保根节点被调度 ==========
  ensureRootIsScheduled(root);

  // 返回继续函数(如果还有工作)
  if (root.callbackNode === originalCallbackNode) {
    return performConcurrentWorkOnRoot.bind(null, root);
  }
  return null;
}

6.2 renderRootConcurrent:可中断渲染

function renderRootConcurrent(root: FiberRoot, lanes: Lanes): RootExitStatus {

  const prevExecutionContext = executionContext;
  executionContext |= RenderContext;

  // ========== 步骤1:准备新的工作栈 ==========
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    prepareFreshStack(root, lanes);

    console.log('准备新的 Fiber 树');
  }

  console.log('========== 开始工作循环 ==========');

  // ========== 步骤2:工作循环(可中断) ==========
  outer: do {
    try {
      if (
        workInProgressSuspendedReason !== NotSuspended &&
        workInProgress !== null
      ) {
        // 处理 Suspense
        // ...
      }

      workLoopConcurrent();  // ← 核心工作循环
      break;

    } catch (thrownValue) {
      // 处理错误
      handleThrow(root, thrownValue);
    }
  } while (true);

  // ========== 步骤3:重置状态 ==========
  executionContext = prevExecutionContext;

  // ========== 步骤4:返回退出状态 ==========
  if (workInProgress !== null) {
    // 还有未完成的工作(被中断)
    return RootInProgress;
  } else {
    // 工作完成
    workInProgressRoot = null;
    workInProgressRootRenderLanes = NoLanes;
    return workInProgressRootExitStatus;
  }
}

6.3 workLoopConcurrent:核心工作循环

function workLoopConcurrent() {
  // 循环处理 Fiber 节点,直到没有工作或需要让出控制权
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

// shouldYield:检查是否应该让出控制权
function shouldYield(): boolean {
  const currentTime = now();
  if (currentTime >= deadline) {
    // 超过时间片(默认 5ms)
    return true;
  }

  // 检查是否有更高优先级的任务
  if (needsPaint || scheduling.isInputPending()) {
    return true;
  }

  return false;
}

6.4 performUnitOfWork:处理单个 Fiber

function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;

  console.log('处理 Fiber:', getComponentNameFromFiber(unitOfWork));

  // ========== 步骤1:beginWork(递阶段) ==========
  let next = beginWork(current, unitOfWork, renderLanes);

  // 更新 props
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    // 没有子节点,完成当前节点
    completeUnitOfWork(unitOfWork);
  } else {
    // 继续处理子节点
    workInProgress = next;
  }
}

6.5 beginWork:处理组件(关键!)

文件位置packages/react-reconciler/src/ReactFiberBeginWork.js

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {

  console.log('beginWork:', workInProgress.tag);

  // ========== 优化:bailout 检查 ==========
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      (__DEV__ && workInProgress.type !== current.type)
    ) {
      // Props 变化,需要更新
      didReceiveUpdate = true;
    } else {
      // Props 未变化,检查是否可以跳过
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );

      if (!hasScheduledUpdateOrContext) {
        // 可以跳过这个组件
        console.log('跳过组件(bailout)');
        return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
      }
    }
  }

  // 清除 lanes(表示正在处理)
  workInProgress.lanes = NoLanes;

  // ========== 根据组件类型处理 ==========
  switch (workInProgress.tag) {
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps = workInProgress.elementType === Component
        ? unresolvedProps
        : resolveDefaultProps(Component, unresolvedProps);

      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }

    case ClassComponent: {
      // 处理类组件
      // ...
    }

    case HostRoot: {
      // 处理根节点
      return updateHostRoot(current, workInProgress, renderLanes);
    }

    case HostComponent: {
      // 处理原生 DOM 元素
      return updateHostComponent(current, workInProgress, renderLanes);
    }

    // ... 其他 32 种类型
  }
}

6.6 updateFunctionComponent:处理函数组件

这是我们的 ProductList 组件被处理的地方!

function updateFunctionComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,  // ProductList 函数
  nextProps: any,
  renderLanes: Lanes,
): Fiber | null {

  console.log('更新函数组件:', Component.name);

  // ========== 步骤1:准备上下文 ==========
  let context;
  if (!disableLegacyContext) {
    const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
    context = getMaskedContext(workInProgress, unmaskedContext);
  }

  // ========== 步骤2:调用组件函数(renderWithHooks) ==========
  let nextChildren = renderWithHooks(
    current,
    workInProgress,
    Component,     // ProductList
    nextProps,     // props
    context,
    renderLanes,
  );

  console.log('组件返回的子元素:', nextChildren);

  // ========== 步骤3:检查是否可以复用 ==========
  if (current !== null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  // ========== 步骤4:协调子元素 ==========
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);

  // ========== 步骤5:返回子 Fiber ==========
  return workInProgress.child;
}

6.7 renderWithHooks:执行 Hooks(核心!)

文件位置packages/react-reconciler/src/ReactFiberHooks.js

export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {

  console.log('========== renderWithHooks ==========');

  // ========== 步骤1:设置全局变量 ==========
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber = workInProgress;

  // 清除状态(将在组件调用时重新构建)
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;

  // ========== 步骤2:设置正确的 Dispatcher ==========
  ReactSharedInternals.H =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount     // 首次渲染
      : HooksDispatcherOnUpdate;   // 更新

  console.log('Dispatcher:', current === null ? 'Mount' : 'Update');

  // ========== 步骤3:调用组件函数 ==========
  console.log('调用组件函数:', Component.name);

  let children = Component(props, secondArg);  // ← ProductList(props)

  console.log('组件执行完成');

  // ========== 步骤4:检查是否有渲染阶段更新 ==========
  if (didScheduleRenderPhaseUpdateDuringThisPass) {
    children = renderWithHooksAgain(workInProgress, Component, props, secondArg);
  }

  // ========== 步骤5:完成渲染 ==========
  finishRenderingHooks(current, workInProgress, Component);

  return children;
}

当执行 Component(props) 时,发生了什么?(关键)

// Component = ProductList
// 执行 ProductList(props)

function ProductList(props) {
  // 第1个 Hook
  const [products, setProducts] = useState([]);
  // ↓ 调用 dispatcher.useState([])
  // ↓ 因为是 Update,调用 updateState
  // ↓ 从 Hook 链表获取第1个 Hook
  // ↓ 处理更新队列(如果有)
  // ↓ 返回 [products, setProducts]

  // 第2个 Hook
  const [filterCategory, setFilterCategory] = useState('all');
  // ↓ 调用 dispatcher.useState('all')
  // ↓ 从 Hook 链表获取第2个 Hook
  // ↓ 处理更新队列 ← 这里有我们的 Update!
  // ↓ queue.pending = Update { action: 'electronics' }
  // ↓ 计算新状态:basicStateReducer('all', 'electronics') → 'electronics'
  // ↓ 返回 ['electronics', setFilterCategory]

  // 第3个 Hook
  const [sortBy, setSortBy] = useState('price');
  // ↓ 从 Hook 链表获取第3个 Hook
  // ↓ 没有更新,返回旧状态

  // 第4个 Hook
  const [loading, setLoading] = useState(false);
  // ↓ 从 Hook 链表获取第4个 Hook

  // 第5个 Hook
  useEffect(() => {
    fetchProducts();
  }, [filterCategory, sortBy]);
  // ↓ 调用 dispatcher.useEffect
  // ↓ 比较依赖:[filterCategory, sortBy]
  // ↓ 旧依赖:['all', 'price']
  // ↓ 新依赖:['electronics', 'price']
  // ↓ filterCategory 变化!标记 HookHasEffect
  // ↓ 这个 effect 会在 Commit 阶段执行

  // ... 组件逻辑

  return (/* JSX */);
}

6.8 reconcileChildren:协调子元素(Diff 算法)

export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  if (current === null) {
    // 首次渲染:直接创建子 Fiber
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } else {
    // 更新:进行 Diff
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}

Diff 算法的三大策略

  1. Tree Diff:只比较同层节点,不跨层比较
  2. Component Diff:相同类型的组件才会继续比较
  3. Element Diff:使用 key 优化列表比较

6.5 Render 阶段性能优化:Lane 标记与 Bailout 机制

疑问:此时,我们不禁想问,假如某个组件 A 调用 setState,会遍历整个项目的所有 Fiber 吗?

答案:不会!React 通过 Lane 标记机制和 bailout 优化,只处理必要的 Fiber 节点。

6.5.1 setState 如何避免遍历整个 Fiber 树


核心机制
组件 A 调用 setState
    ↓
1. 标记路径:从组件 A 向上标记到根节点的所有 Fiber 的 lanes
    ↓
2. 调度更新:从根节点开始遍历
    ↓
3. Bailout 优化:跳过没有标记的子树
    ↓
结果:只处理组件 A 及其子树,其他组件被跳过

示例:复杂组件树的更新
<Root>
  <App>
    <Sidebar>
      <Menu />      // ← 这些组件不会被处理
      <UserInfo />  // ← 这些组件不会被处理
    </Sidebar>
    <Main>
      <Header />
      <Content>
        <ProductList>  // ← 组件 A 在这里调用 setState
          <ProductItem />
          <ProductItem />
        </ProductList>
      </Content>
      <Footer />
    </Main>
  </App>
</Root>

阶段 1:标记路径(markRootUpdated)

ProductList 组件调用 setState 时:

// 在 dispatchSetState 中
function dispatchSetState(fiber, queue, action) {
  // 1. 创建 Update
  const update = { lane: SyncLane, action, next: null };
  queue.pending = update;

  // 2. 调度更新
  const root = enqueueConcurrentHookUpdate(fiber, queue, update, SyncLane);
  if (root !== null) {
    // 关键:标记从 ProductList 到根节点的路径
    scheduleUpdateOnFiber(root, fiber, SyncLane);
  }
}

标记过程

// 1. setState 时:暂存 Update,只标记当前 Fiber
// packages/react-reconciler/src/ReactFiberConcurrentUpdates.js
function enqueueUpdate(fiber, queue, update, lane) {
  // 暂存到数组,稍后批量处理
  concurrentQueues[concurrentQueuesIndex++] = fiber;
  concurrentQueues[concurrentQueuesIndex++] = lane;

  // 只标记当前 Fiber
  fiber.lanes = mergeLanes(fiber.lanes, lane);
  // 此时不向上标记 childLanes
}

// 2. 渲染开始前:批量向上标记 childLanes
// packages/react-reconciler/src/ReactFiberWorkLoop.js
renderRootConcurrent(root, lanes) {
  prepareFreshStack(root, lanes);// packages/react-reconciler/src/ReactFiberConcurrentUpdates.js
  finishQueueingConcurrentUpdates() {
    while (i < endIndex) {
      const fiber = concurrentQueues[i++];
      const lane = concurrentQueues[i++];

      // 在这里调用,向上标记所有父 Fiber
      markUpdateLaneFromFiberToRoot(fiber, update, lane);
    }
  }
}

// 3. 向上标记函数
function markUpdateLaneFromFiberToRoot(sourceFiber, update, lane) {
  sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);

  // 向上遍历,标记所有父 Fiber 的 childLanes
  let parent = sourceFiber.return;
  while (parent !== null) {
    parent.childLanes = mergeLanes(parent.childLanes, lane);

    const alternate = parent.alternate;
    if (alternate !== null) {
      alternate.childLanes = mergeLanes(alternate.childLanes, lane);
    }

    parent = parent.return;
  }
}

标记结果(Fiber 树的 lanes 标记):

RootFiber
  ├─ lanes: NoLanes
  ├─ childLanes: SyncLane 标记:子树有更新
  └─ child → App Fiber
              ├─ lanes: NoLanes
              ├─ childLanes: SyncLane 标记:子树有更新
              └─ child → Sidebar / Main
                          ↓
                    Main Fiber
                      ├─ lanes: NoLanes
                      ├─ childLanes: SyncLane 标记:子树有更新
                      └─ child → Header / Content / Footer
                                  ↓
                            Content Fiber
                              ├─ lanes: NoLanes
                              ├─ childLanes: SyncLane 标记:子树有更新
                              └─ child → ProductList
                                          ↓
                                    ProductList Fiber
                                      ├─ lanes: SyncLane 自己有更新
                                      ├─ childLanes: NoLanes
                                      └─ child → ProductItem...

Sidebar Fiber(未标记)
  ├─ lanes: NoLanes
  ├─ childLanes: NoLanes ← 没有更新
  └─ child → Menu / UserInfo

阶段 2:遍历 Fiber 树(bailout 优化)

调度开始后,从根节点遍历:

// packages/react-reconciler/src/ReactFiberBeginWork.js
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {

  // ========== 优化1:检查当前 Fiber 是否需要更新 ==========
  const updateLanes = workInProgress.lanes;

  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    // 检查是否可以跳过(bailout)
    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      // 强制更新标志
      workInProgress.type !== current.type
    ) {
      // Props 改变了,需要更新
      didReceiveUpdate = true;
    } else {
      // ========== 关键检查:子树是否有更新 ==========
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );

      if (!hasScheduledUpdateOrContext) {
        // 关键,Bailout 优化:跳过当前 Fiber 及其子树
        didReceiveUpdate = false;
        return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
      }

      didReceiveUpdate = false;
    }
  }

  // 清除 lanes(已经处理过了)
  workInProgress.lanes = NoLanes;

  // 继续处理当前 Fiber
  switch (workInProgress.tag) {
    case FunctionComponent:
      return updateFunctionComponent(current, workInProgress, ...);
    // ...
  }
}

checkScheduledUpdateOrContext:检查是否需要更新

function checkScheduledUpdateOrContext(
  current: Fiber,
  renderLanes: Lanes,
): boolean {
  // 检查当前 Fiber 的 lanes 是否包含当前渲染的 lanes
  const updateLanes = current.lanes;
  if (includesSomeLane(updateLanes, renderLanes)) {
    return true;  // 当前 Fiber 有更新
  }

  // 检查子树是否有更新
  const childLanes = current.childLanes;
  if (includesSomeLane(childLanes, renderLanes)) {
    return true;  // 子树有更新
  }

  return false;  // 没有更新,可以 bailout
}

bailoutOnAlreadyFinishedWork:跳过子树

function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {

  if (current !== null) {
    // 复用 current 树的子节点(不重新创建)
    workInProgress.child = current.child;
  }

  // ========== 关键:检查子树是否需要处理 ==========
  if (!includesSomeLane(workInProgress.childLanes, renderLanes)) {
    // 子树也没有更新,直接返回 null
    // 不会继续遍历子树,直接跳过
    return null;
  }

  // 子树有更新,克隆子节点(继续遍历子树)
  cloneChildFibers(current, workInProgress);
  return workInProgress.child;
}

完整的遍历过程(以示例为例)
========== 开始 Render 阶段 ==========
workLoopConcurrent()

1. performUnitOfWork(RootFiber)
   ├─ beginWork(RootFiber)
   │   ├─ childLanes: SyncLane(子树有更新)
   │   └─ 返回 App Fiber ✅ 继续处理
   ↓

2. performUnitOfWork(App Fiber)
   ├─ beginWork(App Fiber)
   │   ├─ lanes: NoLanes(自己无更新)
   │   ├─ childLanes: SyncLane(子树有更新)
   │   ├─ Props 没变
   │   └─ 返回 Sidebar Fiber 继续处理
   ↓

3. performUnitOfWork(Sidebar Fiber)
   ├─ beginWork(Sidebar Fiber)
   │   ├─ lanes: NoLanes(自己无更新)
   │   ├─ childLanes: NoLanes(子树无更新)★★★
   │   ├─ bailoutOnAlreadyFinishedWork
   │   └─ 返回 null 跳过整个 Sidebar 子树
   ↓

4. performUnitOfWork(Main Fiber) ← 处理 Sidebar 的兄弟节点
   ├─ beginWork(Main Fiber)
   │   ├─ lanes: NoLanes
   │   ├─ childLanes: SyncLane(子树有更新)
   │   └─ 返回 Header Fiber 继续处理
   ↓

5. performUnitOfWork(Header Fiber)
   ├─ beginWork(Header Fiber)
   │   ├─ childLanes: NoLanes
   │   └─ bailout,返回 null 跳过
   ↓

6. performUnitOfWork(Content Fiber) ← Header 的兄弟
   ├─ beginWork(Content Fiber)
   │   ├─ childLanes: SyncLane
   │   └─ 返回 ProductList Fiber 
   ↓

7. performUnitOfWork(ProductList Fiber)
   ├─ beginWork(ProductList Fiber)
   │   ├─ lanes: SyncLane  自己有更新
   │   ├─ 重新调用组件函数 ProductList()
   │   ├─ 计算新状态
   │   ├─ reconcileChildren(Diff 子节点)
   │   └─ 返回 ProductItem Fiber 
   ↓

8. performUnitOfWork(ProductItem Fiber)
   ├─ beginWork(ProductItem Fiber)
   │   ├─ Props 可能变了
   │   └─ 重新渲染 
   ↓

9. performUnitOfWork(Footer Fiber) ← Content 的兄弟
   ├─ beginWork(Footer Fiber)
   │   ├─ childLanes: NoLanes
   │   └─ bailout  跳过

统计:实际处理的 Fiber 数量

组件树总共 14 个 Fiber

  • Root, App, Sidebar, Menu, UserInfo, Main, Header, Content, ProductList, ProductItem × 2, Footer (共 12 个)

实际处理的 Fiber

  • Root
  • App
  • Sidebar (bailout,但需要检查)
  • Main
  • Header (bailout)
  • Content
  • ProductList (重新渲染)
  • ProductItem × 2 (可能重新渲染)
  • Footer (bailout)

跳过的 Fiber

  • Menu (Sidebar bailout,整个子树跳过)
  • UserInfo (Sidebar bailout,整个子树跳过)

优化效果

  • 理论上需要遍历 14 个 Fiber
  • 实际上只处理了约 10 个 Fiber(根据 Props 对比可能更少)
  • Menu 和 UserInfo 的整个子树被完全跳过

Lane 标记的传播路径
// setState 调用点:ProductList Fiber

ProductList Fiber
  ↑
  └─ lanes: SyncLane ★ 自己标记

向上传播 childLanes:

ProductList.return → Content Fiber
  ↑
  └─ childLanes |= SyncLane ★

Content.return → Main Fiber
  ↑
  └─ childLanes |= SyncLane ★

Main.return → App Fiber
  ↑
  └─ childLanes |= SyncLane ★

App.return → RootFiber
  ↑
  └─ childLanes |= SyncLane ★

// 标记完成,形成一条从 ProductList 到 Root 的"更新路径"

为什么必须从根节点开始遍历?

原因 1:保证顺序

// 如果直接从 ProductList 开始:
// 1. 无法处理 ProductList 上面的 Context 变化
// 2. 无法处理 ProductList 上面的 Props 传递
// 3. Fiber 树的遍历顺序会错乱

// 正确做法:从根开始,通过 bailout 跳过不需要的分支

原因 2:支持优先级中断

// 低优先级更新进行到一半,来了高优先级更新
// 需要从根重新开始,使用新的优先级
prepareFreshStack(root, highPriorityLanes);
workLoopConcurrent();

原因 3:Context 和 Props 传递

<ThemeContext.Provider value={theme}>  // ← 可能影响子树
  <App>
    <Main>
      <ProductList />  // ← 可能依赖 theme
    </Main>
  </App>
</ThemeContext.Provider>

// 必须从 Provider 开始处理,才能正确传递 Context

优化总结
机制 作用 实现方式
Lane 标记 标记哪些 Fiber 需要更新 finishQueueingConcurrentUpdates 中调用 markUpdateLaneFromFiberToRoot
childLanes 传播 标记更新路径 向上标记所有父 Fiber 的 childLanes
bailout 优化 跳过不需要更新的子树 bailoutOnAlreadyFinishedWork
Props 对比 判断组件是否需要重新渲染 oldProps !== newProps

完整的更新流程图
用户在 ProductList 中调用 setState
    ↓
1. 创建 Update 对象
    ↓
2. 标记 ProductList.lanes = SyncLane
    ↓
3. 向上传播 childLanes
   ProductList → Content → Main → App → Root
    ↓
4. 调度更新(从根开始)
    ↓
5. workLoop 遍历 Fiber 树
   ├─ Root: childLanes 有 SyncLane → 继续
   ├─ App: childLanes 有 SyncLane → 继续
   ├─ Sidebar: childLanes 为空 → bailout(跳过 Menu, UserInfo)
   ├─ Main: childLanes 有 SyncLane → 继续
   ├─ Header: childLanes 为空 → bailout
   ├─ Content: childLanes 有 SyncLane → 继续
   ├─ ProductList: lanes 有 SyncLane → 重新渲染 ★★★
   │   └─ 调用 ProductList(),计算新状态,reconcileChildren
   ├─ ProductItem: 根据 Props 决定是否重新渲染
   └─ Footer: childLanes 为空 → bailout
    ↓
6. Commit 阶段:只更新有变化的 DOM

6.5.2 那么有人就会问了,那组件内部的子组件会被标记和遍历吗

假设:当组件 A 调用 setState,A 内部的子组件是否有 Lane 标记?是否会遍历 A 的所有子组件?

答案:

  1. A 的子组件不会被标记 lanes(Lane 只向上传播,不向下)
  2. 但 A 的所有子组件都会被遍历(因为 A 重新渲染会调用 reconcileChildren)
  3. 子组件是否重新渲染取决于 Props 是否变化

示例:ProductList 及其子组件
function ProductList() {
  const [filter, setFilter] = useState('all');  // ← setState 在这里

  return (
    <div>
      <FilterButton
        active={filter === 'all'}
        onClick={() => setFilter('all')}  // ← 子组件 1
      />
      <ProductItem id={1} />  // ← 子组件 2
      <ProductItem id={2} />  // ← 子组件 3
      <ProductItem id={3} />  // ← 子组件 4
      <Footer />              // ← 子组件 5
    </div>
  );
}

阶段 1:Lane 标记(只向上,不向下)
// 用户调用 setFilter('all')

ProductList Fiber
  ├─ lanes: SyncLane ★★★ 自己被标记
  └─ childLanes: NoLanes

向上传播(childLanes):
  ↑
Content Fiber
  ├─ lanes: NoLanes
  └─ childLanes: SyncLane  传播

Main Fiber
  ├─ lanes: NoLanes
  └─ childLanes: SyncLane  传播

App Fiber
  ├─ lanes: NoLanes
  └─ childLanes: SyncLane  传播

Root Fiber
  ├─ lanes: NoLanes
  └─ childLanes: SyncLane  传播

---

向下查看(子组件):

ProductList Fiber
  ├─ lanes: SyncLane
  └─ child → div Fiber
              ├─ lanes: NoLanes  子组件没有被标记
              └─ child → FilterButton Fiber
                          ├─ lanes: NoLanes  没有标记
                          └─ sibling → ProductItem Fiber
                                        ├─ lanes: NoLanes  没有标记
                                        └─ sibling → ...

关键点:Lane 标记只向上传播(通过 childLanes),不向下传播


阶段 2:遍历过程(子组件会被遍历)
// 从根开始遍历,最终到达 ProductList...

performUnitOfWork(ProductList Fiber)beginWork(ProductList Fiber)
  ├─ lanes: SyncLane  自己有更新
  ├─ 调用 ProductList() 函数 重新执行
  │   ├─ useState 计算新状态: filter = 'all'
  │   └─ 返回新的 ReactElement 树
  ↓
reconcileChildren(ProductList, newChildren)
  ├─ 比较旧的子 Fiber 和新的 ReactElement
  ├─ 创建/复用子 Fiber 节点
  └─ 返回 div Fiber  返回第一个子节点
  ↓
performUnitOfWork(div Fiber)  // ← 继续遍历子组件beginWork(div Fiber)
  ├─ lanes: NoLanes  自己没有标记
  ├─ childLanes: NoLanes
  ├─ 检查 Props 是否变化
  │   ├─ oldProps: { children: [...] }
  │   └─ newProps: { children: [...] }
  ↓
  如果 Props 没变:
    └─ bailoutOnAlreadyFinishedWork
        ├─ 复用旧的子 Fiber
        └─ 返回 FilterButton Fiber  继续遍历子节点
  ↓
performUnitOfWork(FilterButton Fiber)  // ← 继续遍历beginWork(FilterButton Fiber)
  ├─ lanes: NoLanes
  ├─ 检查 Props 是否变化
  │   ├─ oldProps: { active: false, onClick: fn1 }
  │   └─ newProps: { active: true, onClick: fn2 }   Props 变了
  ↓
  Props 变了,需要重新渲染:
    ├─ 调用 FilterButton(newProps) 
    ├─ reconcileChildren
    └─ 返回子 Fiber
  ↓
performUnitOfWork(ProductItem Fiber id=1)beginWork(ProductItem Fiber)
  ├─ lanes: NoLanes
  ├─ 检查 Props
  │   ├─ oldProps: { id: 1 }
  │   └─ newProps: { id: 1 }   Props 没变
  ↓
  Props 没变,bailout:
    ├─ 不调用 ProductItem 函数
    ├─ 复用旧的子 Fiber
    └─ 返回子节点(如果有)
  ↓
performUnitOfWork(ProductItem Fiber id=2)
  // 同上,Props 没变,bailoutperformUnitOfWork(ProductItem Fiber id=3)
  // 同上,Props 没变,bailoutperformUnitOfWork(Footer Fiber)
  // Props 没变,bailout

完整的遍历路径图
ProductList.setState()
  ↓
========== Lane 标记(向上) ==========
ProductList.lanes = SyncLane
ProductList → Content → Main → App → Root (childLanes)

========== 遍历开始(从根向下) ==========

Root → App → Main → Content → ProductList ★ 重新渲染
  ↓
ProductList() 执行
  ├─ useState 计算新状态
  └─ 返回新的 JSX 树
  ↓
reconcileChildren ★ 创建/更新子 Fiber
  ↓
========== 遍历 ProductList 的子组件 ==========

1. div Fiber
   ├─ lanes: NoLanes (没有 Lane 标记)
   ├─ Props 对比 → 可能 bailout
   └─ 遍历  (需要检查子节点)

2. FilterButton Fiber
   ├─ lanes: NoLanes (没有 Lane 标记)
   ├─ Props 对比 → active 变了
   └─ 重新渲染 

3. ProductItem Fiber × 3
    ├─ lanes: NoLanes (没有 Lane 标记)
    ├─ Props 对比 → 没变
    └─ bailout  (不重新渲染)

关键代码:beginWork 中的 Props 对比
// packages/react-reconciler/src/ReactFiberBeginWork.js

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {

  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    // ========== 关键检查 1:Props 是否变化 ==========
    if (
      oldProps !== newProps ||  //  引用对比(浅比较)
      hasLegacyContextChanged() ||
      workInProgress.type !== current.type
    ) {
      // Props 变了,需要重新渲染
      didReceiveUpdate = true;
    } else {
      // Props 没变,检查是否有 Lane 标记
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );

      if (!hasScheduledUpdateOrContext) {
        // ========== 没有更新,可以 bailout ==========
        didReceiveUpdate = false;
        return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
      }

      didReceiveUpdate = false;
    }
  } else {
    // 首次渲染
    didReceiveUpdate = false;
  }

  // 清除已处理的 lanes
  workInProgress.lanes = NoLanes;

  // ========== 根据组件类型处理 ==========
  switch (workInProgress.tag) {
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        unresolvedProps,
        renderLanes,
      );
    }
    // ...
  }
}

为什么子组件会被遍历但不一定重新渲染?

遍历(Traverse)重新渲染(Re-render) 是两个不同的概念:

操作 含义 开销
遍历(performUnitOfWork) 访问 Fiber 节点,执行 beginWork 很小(只是函数调用和对比)
重新渲染(调用组件函数) 执行 Component(props),生成新的 ReactElement 较大(执行用户代码、创建对象)
bailout 跳过重新渲染,复用旧的子 Fiber 最小(直接复用)

示例:实际执行情况
function ProductList() {
  console.log('ProductList 渲染');  // ← 会打印
  const [filter, setFilter] = useState('all');

  return (
    <div>
      <FilterButton
        active={filter === 'all'}  // ← Props 变了
        onClick={() => setFilter('all')}  // ← 新函数引用
      />
      <ProductItem id={1} />  // ← Props 没变
    </div>
  );
}

function FilterButton({ active, onClick }) {
  console.log('FilterButton 渲染');  // ← 会打印(Props 变了)
  return <button className={active ? 'active' : ''} onClick={onClick} />;
}

function ProductItem({ id }) {
  console.log('ProductItem 渲染', id);  // ← 不会打印(bailout)
  return <div>Product {id}</div>;
}

点击后的控制台输出:

ProductList 渲染
FilterButton 渲染

ProductItem 不会打印 → 因为 Props 没变,发生了 bailout


bailout 的条件
// packages/react-reconciler/src/ReactFiberBeginWork.js

// bailout 需要同时满足:
if (
  oldProps === newProps &&           // 1. Props 引用相同(浅比较)
  !hasLegacyContextChanged() &&      // 2. Context 没变
  workInProgress.type === current.type &&  // 3. 组件类型没变
  !includesSomeLane(renderLanes, updateLanes)  // 4. 没有 Lane 标记
) {
  // 可以 bailout
  return bailoutOnAlreadyFinishedWork(...);
}

常见的破坏 bailout 的写法
function ProductList() {
  const [filter, setFilter] = useState('all');

  return (
    <div>
      {/* 错误: 每次渲染都创建新对象 → Props 总是变化 */}
      <ProductItem data={{ id: 1, name: 'Product 1' }} />

      {/* 错误: 每次渲染都创建新函数 → Props 总是变化 */}
      <FilterButton onClick={() => setFilter('all')} />

      {/* 错误: 每次渲染都创建新数组 → Props 总是变化 */}
      <ProductList items={[1, 2, 3]} />

      {/* 正确: 原始值不变 → 可以 bailout */}
      <ProductItem id={1} name="Product 1" />

      {/* 正确: 使用 useCallback 缓存函数 → 可以 bailout */}
      <FilterButton onClick={handleClick} />

      {/* 正确: 使用 useMemo 缓存对象 → 可以 bailout */}
      <ProductItem data={memoizedData} />
    </div>
  );
}

优化:React.memo 和 useMemo
// 优化前:每次都重新渲染
function ProductItem({ id, name }) {
  console.log('渲染', id);
  return <div>{name}</div>;
}

// 优化后:Props 没变就不重新渲染
const ProductItem = React.memo(function ProductItem({ id, name }) {
  console.log('渲染', id);
  return <div>{name}</div>;
});

// React.memo 的原理:在 bailout 前额外做一次 Props 浅比较
// 默认使用 shallowEqual,也可以传入自定义比较函数

完整流程总结
ProductList.setState()
  ↓
【标记阶段】
  ├─ ProductList.lanes = SyncLane 
  ├─ 向上传播 childLanes 到根
  └─ 子组件不会被标记 
  ↓
【遍历阶段】
  ├─ 从根开始遍历
  ├─ 通过 childLanes 找到 ProductList
  ├─ ProductList 重新渲染 
  │   └─ reconcileChildren → 更新子 Fiber
  ↓
【子组件处理】
  ├─ div Fiber
  │   ├─ lanes: NoLanes (无标记)
  │   ├─ Props 对比 → 可能 bailout
  │   └─ 遍历  (需要检查子节点)
  │
  ├─ FilterButton Fiber
  │   ├─ lanes: NoLanes (无标记)
  │   ├─ Props 对比 → active 变了
  │   └─ 重新渲染 
  │
  └─ ProductItem Fiber × 3
      ├─ lanes: NoLanes (无标记)
      ├─ Props 对比 → 没变
      └─ bailout  (不重新渲染)

性能对比

场景:ProductList 有 100 个 ProductItem 子组件

function ProductList() {
  const [filter, setFilter] = useState('all');

  return (
    <div>
      {Array.from({ length: 100 }, (_, i) => (
        <ProductItem key={i} id={i} />
      ))}
    </div>
  );
}

没有优化(每次传递新对象):

<ProductItem key={i} data={{ id: i }} />  // ← 每次新对象

// 结果:
// - 101 个 Fiber 被遍历(ProductList + 100 个 ProductItem)
// - 101 个组件被重新渲染
// - 耗时:~10ms(假设)

有优化(Props 不变):

<ProductItem key={i} id={i} />  // ← 原始值不变

// 结果:
// - 101 个 Fiber 被遍历
// - 1 个组件被重新渲染(只有 ProductList)
// - 100 个组件 bailout
// - 耗时:~1ms(假设)

使用 React.memo

const ProductItem = React.memo(function ProductItem({ id }) {
  return <div>Product {id}</div>;
});

// 结果:
// - 101 个 Fiber 被遍历
// - 1 个组件被重新渲染
// - 100 个组件 bailout(React.memo 提前检查)
// - 耗时:~0.8ms(假设,memo 检查更轻量)

关键要点
问题 答案
子组件会被标记 lanes 吗? 不会,lanes 只向上传播
子组件会被遍历吗? 会,因为父组件重新渲染
子组件一定会重新渲染吗? 不一定,取决于 Props 是否变化
如何避免子组件重新渲染? 使用 React.memo、useMemo、useCallback
bailout 会完全跳过子树吗? 部分跳过,仍会执行 beginWork,但不调用组件函数
遍历和渲染的区别? 遍历:访问 Fiber;渲染:调用组件函数

7. Commit阶段的完整流程

7.1 finishConcurrentRender:准备提交

function finishConcurrentRender(
  root: FiberRoot,
  exitStatus: RootExitStatus,
  finishedWork: Fiber,
  lanes: Lanes,
): void {

  console.log('========== 准备提交 ==========');

  // 根据退出状态处理
  switch (exitStatus) {
    case RootCompleted: {
      // 渲染完成,提交
      commitRoot(
        root,
        workInProgressRootRecoverableErrors,
        workInProgressTransitions,
        workInProgressDeferredLane,
      );
      break;
    }

    // ... 其他状态
  }
}

7.2 commitRoot:提交阶段入口

文件位置packages/react-reconciler/src/ReactFiberWorkLoop.js

function commitRoot(
  root: FiberRoot,
  recoverableErrors: null | Array<CapturedValue<mixed>>,
  transitions: Array<Transition> | null,
  renderPriorityLevel: Lane,
): void {

  console.log('========== 开始 Commit 阶段 ==========');

  // ========== 步骤1:准备提交 ==========
  const previousUpdateLanePriority = getCurrentUpdatePriority();
  const prevTransition = ReactSharedInternals.T;

  try {
    ReactSharedInternals.T = null;
    setCurrentUpdatePriority(DiscreteEventPriority);

    // ========== 步骤2:执行提交 ==========
    commitRootImpl(
      root,
      recoverableErrors,
      transitions,
      renderPriorityLevel,
    );

  } finally {
    ReactSharedInternals.T = prevTransition;
    setCurrentUpdatePriority(previousUpdateLanePriority);
  }
}

7.3 commitRootImpl:提交核心逻辑

function commitRootImpl(
  root: FiberRoot,
  recoverableErrors: null | Array<CapturedValue<mixed>>,
  transitions: Array<Transition> | null,
  renderPriorityLevel: Lane,
): void {

  const finishedWork = root.finishedWork;
  const lanes = root.finishedLanes;

  console.log('Commit 的 Fiber 树:', finishedWork);

  // ========== 步骤1:执行被动效果(useEffect) ==========
  // 在开始之前先清理上一次的 effects
  do {
    flushPassiveEffects();
  } while (rootWithPendingPassiveEffects !== null);

  // ========== 步骤2:准备提交 ==========
  root.finishedWork = null;
  root.finishedLanes = NoLanes;

  const subtreeHasEffects =
    (finishedWork.subtreeFlags &
      (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
    NoFlags;
  const rootHasEffect =
    (finishedWork.flags &
      (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
    NoFlags;

  if (subtreeHasEffects || rootHasEffect) {

    const prevExecutionContext = executionContext;
    executionContext |= CommitContext;

    // ========== 阶段1:Before Mutation(DOM 变更前) ==========
    console.log('===== Before Mutation 阶段 =====');

    commitBeforeMutationEffects(root, finishedWork);

    // ========== 阶段2:Mutation(DOM 变更) ==========
    console.log('===== Mutation 阶段 =====');

    commitMutationEffects(root, finishedWork, lanes);

    // ========== 切换 current 树 ==========
    root.current = finishedWork;
    console.log('current 树已切换');

    // ========== 阶段3:Layout(DOM 变更后) ==========
    console.log('===== Layout 阶段 =====');

    commitLayoutEffects(finishedWork, root, lanes);

    executionContext = prevExecutionContext;

  } else {
    // 没有副作用,直接切换
    root.current = finishedWork;
  }

  // ========== 步骤3:调度被动效果 ==========
  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;

  if (rootDoesHavePassiveEffects) {
    rootDoesHavePassiveEffects = false;
    rootWithPendingPassiveEffects = root;
    pendingPassiveEffectsLanes = lanes;

    // 调度异步执行 useEffect
    scheduleCallback(NormalPriority, () => {
      flushPassiveEffects();
      return null;
    });
  }

  // ========== 步骤4:清理工作 ==========
  remainingLanes = root.pendingLanes;

  // 确保有后续工作被调度
  ensureRootIsScheduled(root);

  // 处理错误
  if (recoverableErrors !== null) {
    // 报告可恢复的错误
    // ...
  }

  console.log('========== Commit 完成 ==========');
}

7.4 Before Mutation 阶段

export function commitBeforeMutationEffects(
  root: FiberRoot,
  firstChild: Fiber,
): boolean {

  focusedInstanceHandle = prepareForCommit(root.containerInfo);

  nextEffect = firstChild;
  commitBeforeMutationEffects_begin();

  const shouldFire = commitBeforeMutationEffects_complete();

  focusedInstanceHandle = null;

  return shouldFire;
}

function commitBeforeMutationEffects_begin() {
  while (nextEffect !== null) {
    const fiber = nextEffect;

    const child = fiber.child;
    if (
      (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
      child !== null
    ) {
      // 继续遍历子节点
      nextEffect = child;
    } else {
      commitBeforeMutationEffects_complete();
    }
  }
}

function commitBeforeMutationEffects_complete() {
  while (nextEffect !== null) {
    const fiber = nextEffect;

    try {
      commitBeforeMutationEffectsOnFiber(fiber);
    } catch (error) {
      captureCommitPhaseError(fiber, fiber.return, error);
    }

    const sibling = fiber.sibling;
    if (sibling !== null) {
      nextEffect = sibling;
      return;
    }

    nextEffect = fiber.return;
  }
}

function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
  const current = finishedWork.alternate;
  const flags = finishedWork.flags;

  switch (finishedWork.tag) {
    case ClassComponent: {
      if ((flags & Snapshot) !== NoFlags) {
        if (current !== null) {
          // 调用 getSnapshotBeforeUpdate
          const instance = finishedWork.stateNode;
          const snapshot = instance.getSnapshotBeforeUpdate(
            prevProps,
            prevState,
          );
          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
        }
      }
      return;
    }
    // ... 其他类型
  }
}

7.5 Mutation 阶段(DOM 操作)

文件位置packages/react-reconciler/src/ReactFiberCommitWork.js

export function commitMutationEffects(
  root: FiberRoot,
  finishedWork: Fiber,
  committedLanes: Lanes,
): void {

  inProgressLanes = committedLanes;
  inProgressRoot = root;

  commitMutationEffectsOnFiber(finishedWork, root, committedLanes);

  inProgressLanes = null;
  inProgressRoot = null;
}

function commitMutationEffectsOnFiber(
  finishedWork: Fiber,
  root: FiberRoot,
  lanes: Lanes,
): void {

  const current = finishedWork.alternate;
  const flags = finishedWork.flags;

  console.log('Mutation:', getComponentNameFromFiber(finishedWork), flags);

  // ========== 处理不同类型的副作用 ==========

  // 处理 Ref
  if (flags & Ref) {
    if (current !== null) {
      commitDetachRef(current);
    }
  }

  // 处理 ContentReset(文本内容重置)
  if (flags & ContentReset) {
    commitResetTextContent(finishedWork);
  }

  // 处理 Update(更新 DOM 属性)
  if (flags & Update) {
    const instance: Instance = finishedWork.stateNode;
    if (instance != null) {
      const newProps = finishedWork.memoizedProps;
      const oldProps = current !== null ? current.memoizedProps : newProps;
      const type = finishedWork.type;
      const updatePayload = finishedWork.updateQueue;

      finishedWork.updateQueue = null;

      if (updatePayload !== null) {
        // 提交 DOM 更新
        commitUpdate(
          instance,
          updatePayload,
          type,
          oldProps,
          newProps,
          finishedWork,
        );

        console.log('更新 DOM 属性:', type, updatePayload);
      }
    }
  }

  // 处理 Deletion(删除节点)
  if (flags & ChildDeletion) {
    const deletions = finishedWork.deletions;
    if (deletions !== null) {
      for (let i = 0; i < deletions.length; i++) {
        const childToDelete = deletions[i];
        commitDeletion(root, childToDelete, finishedWork);
        console.log('删除节点:', getComponentNameFromFiber(childToDelete));
      }
    }
  }

  // 处理 Placement(插入节点)
  if (flags & Placement) {
    commitPlacement(finishedWork);
    finishedWork.flags &= ~Placement;
    console.log('插入节点:', getComponentNameFromFiber(finishedWork));
  }

  // 递归处理子节点
  const child = finishedWork.child;
  if (child !== null) {
    commitMutationEffectsOnFiber(child, root, lanes);
  }

  // 处理兄弟节点
  const sibling = finishedWork.sibling;
  if (sibling !== null) {
    commitMutationEffectsOnFiber(sibling, root, lanes);
  }
}

commitUpdate:更新 DOM 属性

// 文件位置:react-dom-bindings/src/client/ReactDOMComponent.js
export function commitUpdate(
  domElement: Instance,
  updatePayload: Array<mixed>,
  type: string,
  oldProps: Props,
  newProps: Props,
  internalInstanceHandle: Object,
): void {

  // updatePayload 格式:[key1, value1, key2, value2, ...]
  // 例如:['className', 'active', 'style', { color: 'red' }]

  updateProperties(domElement, updatePayload, type, oldProps, newProps);

  // 更新 Fiber 引用
  updateFiberProps(domElement, newProps);
}

function updateProperties(
  domElement: Element,
  updatePayload: Array<any>,
  tag: string,
  lastRawProps: Object,
  nextRawProps: Object,
): void {

  // 遍历 updatePayload,更新属性
  for (let i = 0; i < updatePayload.length; i += 2) {
    const propKey = updatePayload[i];
    const propValue = updatePayload[i + 1];

    if (propKey === 'style') {
      // 更新样式
      setValueForStyles(domElement, propValue);
    } else if (propKey === 'children') {
      // 更新文本内容
      setTextContent(domElement, propValue);
    } else {
      // 更新其他属性
      setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
    }

    console.log('更新属性:', propKey, propValue);
  }
}

commitPlacement:插入 DOM 节点

function commitPlacement(finishedWork: Fiber): void {
  // 步骤1:找到父 DOM 节点
  const parentFiber = getHostParentFiber(finishedWork);

  let parent;
  switch (parentFiber.tag) {
    case HostComponent:
      parent = parentFiber.stateNode;
      break;
    case HostRoot:
      parent = parentFiber.stateNode.containerInfo;
      break;
    // ...
  }

  // 步骤2:找到插入位置(before 节点)
  const before = getHostSibling(finishedWork);

  // 步骤3:插入节点
  insertOrAppendPlacementNode(finishedWork, before, parent);
}

function insertOrAppendPlacementNode(
  node: Fiber,
  before: Instance | null,
  parent: Instance,
): void {
  const { tag } = node;

  const isHost = tag === HostComponent || tag === HostText;

  if (isHost) {
    const stateNode = node.stateNode;

    if (before) {
      // 插入到 before 之前
      insertBefore(parent, stateNode, before);
      console.log('insertBefore:', parent, stateNode, before);
    } else {
      // 追加到末尾
      appendChild(parent, stateNode);
      console.log('appendChild:', parent, stateNode);
    }
  } else {
    // 不是 Host 节点,继续查找子节点
    const child = node.child;
    if (child !== null) {
      insertOrAppendPlacementNode(child, before, parent);

      let sibling = child.sibling;
      while (sibling !== null) {
        insertOrAppendPlacementNode(sibling, before, parent);
        sibling = sibling.sibling;
      }
    }
  }
}

7.6 Layout 阶段

export function commitLayoutEffects(
  finishedWork: Fiber,
  root: FiberRoot,
  committedLanes: Lanes,
): void {

  inProgressLanes = committedLanes;
  inProgressRoot = root;

  nextEffect = finishedWork;

  commitLayoutEffects_begin(finishedWork, root, committedLanes);

  inProgressLanes = null;
  inProgressRoot = null;
}

function commitLayoutEffectOnFiber(
  finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedLanes: Lanes,
): void {

  const flags = finishedWork.flags;

  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
      // 执行 useLayoutEffect
      if ((flags & (Update | Callback)) !== NoFlags) {
        commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
      }
      break;
    }

    case ClassComponent: {
      const instance = finishedWork.stateNode;

      if (flags & Update) {
        if (current === null) {
          // 首次渲染:调用 componentDidMount
          instance.componentDidMount();
        } else {
          // 更新:调用 componentDidUpdate
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          instance.componentDidUpdate(
            prevProps,
            prevState,
            instance.__reactInternalSnapshotBeforeUpdate,
          );
        }
      }

      // 执行 setState 的回调
      const updateQueue = finishedWork.updateQueue;
      if (updateQueue !== null) {
        commitUpdateQueue(finishedWork, updateQueue, instance);
      }
      break;
    }

    case HostComponent: {
      // 更新 ref
      if (flags & Ref) {
        commitAttachRef(finishedWork);
      }
      break;
    }

    // ... 其他类型
  }
}

// 执行 useLayoutEffect
function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
  try {
    commitHookEffectListMount(hookFlags, finishedWork);
  } catch (error) {
    captureCommitPhaseError(finishedWork, finishedWork.return, error);
  }
}

function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
  const updateQueue = finishedWork.updateQueue;
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;

  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;

    do {
      if ((effect.tag & flags) === flags) {
        // 执行 effect 函数
        const create = effect.create;
        const inst = effect.inst;

        const destroy = create();
        inst.destroy = destroy;  // 保存清理函数

        console.log('执行 useLayoutEffect:', create);
      }

      effect = effect.next;
    } while (effect !== firstEffect);
  }
}

8. 批量更新机制

此时,我们来思考一个问题,如果我多次调用setState,会触发多次渲染吗?答案很明显是不会的,正常情况下,只会触发一次渲染,那么这是为什么呢。那么我们就需要了解下批量更新机制了。

8.1 什么是批量更新

批量更新:多次 setState 调用,只触发一次渲染。

function handleClick() {
  setCount(count + 1);     // 不会立即渲染
  setName('React');        // 不会立即渲染
  setLoading(false);       // 不会立即渲染
  // 三次 setState 只会触发一次渲染
}

每次 setState 都会创建 Update 对象并调用 Scheduler 吗?

回答

  1. 每次 setState 确实会创建一个 Update 对象
  2. 但不是每次都会立即调用 Scheduler,React 有**批量更新(Batch Update)**机制

完整流程图
用户代码: setState(newValue)
    ↓
dispatchSetState (创建 Update 对象)
    ↓
const update = {
  lane,              // 优先级
  action: newValue,  // 新状态值
  next: null         // 指向下一个 Update(循环链表)
}
    ↓
加入 Hook.queue.pending (循环链表)
    ↓
scheduleUpdateOnFiber (调度更新)
    ↓
【批量更新检查】
    - 在事件处理函数中? → 批量处理,延迟调用 Scheduler
    - 其他情况? → 立即调用 Scheduler
    ↓
ensureRootIsScheduled
    ↓
Scheduler.scheduleCallback (只调用一次!)

示例 1:单次 setState
function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(1); // ← 只有这一次 setState
  };

  return <button onClick={handleClick}>{count}</button>;
}

执行流程

// 1. 用户点击按钮,调用 handleClick
handleClick()

// 2. 执行 setCount(1)
dispatchSetState(fiber, queue, 1) {
  // 创建 Update 对象
  const update = {
    lane: SyncLane,
    action: 1,
    next: null
  };

  // 加入 Hook 的更新队列(循环链表)
  const pending = queue.pending;
  if (pending === null) {
    update.next = update; // 指向自己,形成循环
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;

  // 3. 调度更新
  scheduleUpdateOnFiber(fiber.alternate, fiber, SyncLane);
}

// 4. 调用 Scheduler
ensureRootIsScheduled(root) {
  Scheduler.scheduleCallback(
    ImmediatePriority,
    performSyncWorkOnRoot.bind(null, root)
  );
}

结果

  • 创建 1 个 Update 对象
  • 调用 1 次 Scheduler

示例 2:多次 setState(批量更新)
function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(1); // ← 第 1 次
    setCount(2); // ← 第 2 次
    setCount(3); // ← 第 3 次
  };

  return <button onClick={handleClick}>{count}</button>;
}

执行流程

// React 18+ 事件处理中的批量更新
handleClick() {
  // React 内部设置批量更新标志
  executionContext |= BatchedContext;

  // 第 1 次 setCount(1)
  dispatchSetState(..., 1) {
    const update1 = { lane: SyncLane, action: 1, next: null };
    update1.next = update1; // 循环链表:update1 → update1
    queue.pending = update1;

    scheduleUpdateOnFiber(...); // ← 检测到批量更新,暂不调用 Scheduler
  }

  // 第 2 次 setCount(2)
  dispatchSetState(..., 2) {
    const update2 = { lane: SyncLane, action: 2, next: null };
    update2.next = update1.next; // update2 → update1
    update1.next = update2;      // update1 → update2
    queue.pending = update2;     // 循环链表:update2 → update1 → update2

    scheduleUpdateOnFiber(...); // ← 检测到批量更新,暂不调用 Scheduler
  }

  // 第 3 次 setCount(3)
  dispatchSetState(..., 3) {
    const update3 = { lane: SyncLane, action: 3, next: null };
    update3.next = update2.next; // update3 → update1
    update2.next = update3;      // update2 → update3
    queue.pending = update3;     // 循环链表:update3 → update2 → update1 → update3

    scheduleUpdateOnFiber(...); // ← 检测到批量更新,暂不调用 Scheduler
  }

  // 事件处理函数执行完毕
  executionContext &= ~BatchedContext; // 清除批量更新标志

  // 现在才调用 Scheduler(只调用一次!)
  ensureRootIsScheduled(root) {
    Scheduler.scheduleCallback(
      ImmediatePriority,
      performSyncWorkOnRoot.bind(null, root)
    );
  }
}

结果

  • 创建 3 个 Update 对象(形成循环链表)
  • 调用 1 次 Scheduler(批量更新)

Update 对象与 Fiber 的关系
FiberRootNode (根节点)
    ↓
FiberNode (如 App 组件的 Fiber)
    ↓
memoizedState → Hook 链表 (每个 Hook 一个节点)
    ↓
Hook {
  memoizedState: 0,        // 当前状态值
  baseState: 0,            // 基础状态
  queue: UpdateQueue {     // 更新队列 ★★★
    pending: Update,       // 待处理的 Update 循环链表
    dispatch: dispatchSetState
  },
  next: Hook               // 下一个 Hook
}
    ↓
UpdateQueue.pending → Update 循环链表
    ↓
Update {
  lane: SyncLane,          // 优先级
  action: 3,               // 新状态值或更新函数
  next: Update             // 下一个 Update(循环链表)
}

关键关系

  1. Fiber.memoizedState → Hook 链表(每个 useState 对应一个 Hook)
  2. Hook.queue → UpdateQueue(存储所有 setState 创建的 Update)
  3. UpdateQueue.pending → Update 循环链表(用 next 指针连接)
  4. 一个 Fiber 可以有多个 Hook(多个 useState)
  5. 一个 Hook 可以有多个 Update(多次 setState)

批量更新的源码实现

文件位置packages/react-reconciler/src/ReactFiberHooks.js

function dispatchSetState(fiber, queue, action) {
  // 1. 创建 Update 对象
  const lane = requestUpdateLane(fiber);
  const update = {
    lane,
    action,
    hasEagerState: false,
    eagerState: null,
    next: null,
  };

  // 2. 加入循环链表
  const pending = queue.pending;
  if (pending === null) {
    // 第一个 Update,指向自己形成循环
    update.next = update;
  } else {
    // 插入链表:update → pending.next → ... → pending → update
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;

  // 3. 优化:提前计算状态(eager state)
  const alternate = fiber.alternate;
  if (
    fiber.lanes === NoLanes &&
    (alternate === null || alternate.lanes === NoLanes)
  ) {
    // 当前没有其他更新,可以立即计算新状态
    const lastRenderedReducer = queue.lastRenderedReducer;
    const currentState = queue.lastRenderedState;
    const eagerState = lastRenderedReducer(currentState, action);

    update.hasEagerState = true;
    update.eagerState = eagerState;

    // 如果状态没变,不需要调度(优化!)
    if (Object.is(eagerState, currentState)) {
      return; // ← 直接返回,不调度更新
    }
  }

  // 4. 调度更新
  const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
  if (root !== null) {
    scheduleUpdateOnFiber(root, fiber, lane); // ← 这里会检查批量更新
  }
}

批量更新检查

// packages/react-reconciler/src/ReactFiberWorkLoop.js
export function scheduleUpdateOnFiber(root, fiber, lane) {
  markRootUpdated(root, lane);

  // 检查当前执行上下文
  if ((executionContext & BatchedContext) !== NoContext) {
    // 在批量更新上下文中,暂不调用 Scheduler
    return;
  }

  // 否则立即调度
  ensureRootIsScheduled(root);
}

批量更新总结
场景 Update 对象数量 Scheduler 调用次数 说明
单次 setState 1 个 1 次 立即调度
事件处理中多次 setState N 个 1 次 批量更新,只在事件结束后调用一次
setTimeout 中多次 setState (React 17) N 个 N 次 每次都调度(无批量更新)
setTimeout 中多次 setState (React 18+) N 个 1 次 自动批量更新(Automatic Batching)

核心要点

  1. 每次 setState 都创建 Update 对象
  2. 不是每次都调用 Scheduler,批量更新会合并多次 setState
  3. Update 对象存储在 Fiber → Hook → UpdateQueue.pending 循环链表中
  4. React 18 引入 Automatic Batching,所有场景都支持批量更新

8.3 React 18 的自动批处理

React 18 之前

// 在事件处理中:批处理 ✓
handleClick() {
  setCount(1);
  setName('A');
  // 只渲染一次
}

// 在 setTimeout 中:不批处理 ✗
setTimeout(() => {
  setCount(1);   // 渲染一次
  setName('A');  // 又渲染一次
}, 1000);

// 在 Promise 中:不批处理 ✗
fetch('/api').then(() => {
  setCount(1);   // 渲染一次
  setName('A');  // 又渲染一次
});

React 18:所有情况都自动批处理 ✓

// 事件处理:批处理
handleClick() {
  setCount(1);
  setName('A');
}

// setTimeout:也批处理
setTimeout(() => {
  setCount(1);
  setName('A');
}, 1000);

// Promise:也批处理
fetch('/api').then(() => {
  setCount(1);
  setName('A');
});

8.4 批处理的实现原理

关键变量executionContext

// packages/react-reconciler/src/ReactFiberWorkLoop.js

let executionContext: ExecutionContext = NoContext;

const NoContext = 0b000;
const BatchedContext = 0b001;       // 批处理上下文
const RenderContext = 0b010;        // 渲染上下文
const CommitContext = 0b100;        // 提交上下文

事件处理中的批处理

// 文件位置:react-dom-bindings/src/events/ReactDOMUpdateBatching.js

export function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
  const prevExecutionContext = executionContext;

  // 设置批处理上下文
  executionContext |= BatchedContext;

  try {
    // 执行事件处理函数
    return fn(a);

  } finally {
    // 恢复上下文
    executionContext = prevExecutionContext;

    // 如果没有其他上下文,刷新同步工作
    if (executionContext === NoContext) {
      flushSyncWorkOnAllRoots();
    }
  }
}

在批处理上下文中调用 setState

// ensureRootIsScheduled 中的逻辑

function ensureRootIsScheduled(root: FiberRoot): void {
  // ...

  if (executionContext & (RenderContext | CommitContext)) {
    // 在渲染或提交阶段,不立即调度(关键)
    return;
  }

  // 调度更新
  scheduleCallback(...);
}

8.5 flushSync:同步更新

如果需要立即更新(不批处理):

import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() => {
    setCount(count + 1);  // 立即渲染
  });

  console.log(ref.current.value);  // 可以读取到最新的 DOM

  flushSync(() => {
    setName('React');  // 又立即渲染
  });
}

flushSync 实现

export function flushSync<R>(fn: () => R): R {
  const prevExecutionContext = executionContext;

  // 添加 BatchedContext,防止嵌套 flushSync
  executionContext |= BatchedContext;

  // 设置同步优先级
  const prevUpdatePriority = getCurrentUpdatePriority();
  setCurrentUpdatePriority(DiscreteEventPriority);

  try {
    // 执行函数
    return fn();

  } finally {
    // 恢复优先级
    setCurrentUpdatePriority(prevUpdatePriority);
    executionContext = prevExecutionContext;

    // 立即刷新所有同步工作
    if (executionContext === NoContext) {
      flushSyncWorkOnAllRoots();
    }
  }
}

9. 完整时序图

9.1 从点击到 DOM 更新的完整流程

用户点击"电子产品"按钮
    ↓
onClick('electronics')
    ↓
handleCategoryChange('electronics')
    ↓
setFilterCategory('electronics')
    ↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 1: setState 调用阶段(同步)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
dispatchSetState(fiber, queue, 'electronics')
    ├─ 计算优先级:lane = DefaultLane
    ├─ 创建 Update 对象
    ├─ 预计算新状态:'electronics'
    └─ dispatchSetStateInternal
           ├─ enqueueConcurrentHookUpdate
           │   └─ 将 Update 加入 queue.pending(环形链表)
           └─ scheduleUpdateOnFiber
                  ├─ markRootUpdated(root, DefaultLane)
                  │   └─ root.pendingLanes |= DefaultLane
                  └─ ensureRootIsScheduled(root)
                         ├─ getNextLanes → DefaultLane
                         ├─ lanesToSchedulerPriority → NormalPriority
                         └─ scheduleCallback(
                              NormalPriority,
                              performConcurrentWorkOnRoot
                            )
    ↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 2: Scheduler 调度阶段(异步)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
【等待浏览器空闲或高优先级任务】
    ↓
Scheduler 执行任务:performConcurrentWorkOnRoot(root)
    ↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 3: Render 阶段(可中断)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
renderRootConcurrent(root, DefaultLane)
    ├─ prepareFreshStack(root, DefaultLane)
    │   ├─ 创建 workInProgress 树
    │   └─ workInProgress = HostRootFiber'
    └─ workLoopConcurrent()
           │
           ↓ while (workInProgress !== null && !shouldYield())
           │
           performUnitOfWork(workInProgress)
               │
               ├─ beginWork(current, workInProgress, DefaultLane)
               │   │
               │   ├─ HostRoot → updateHostRoot
               │   │   ├─ 处理 updateQueue
               │   │   ├─ reconcileChildren
               │   │   └─ return child (App Fiber)
               │   │
               │   ├─ App → updateFunctionComponent
               │   │   ├─ renderWithHooks(App)
               │   │   ├─ 执行 App 函数
               │   │   ├─ reconcileChildren
               │   │   └─ return child (Header Fiber)
               │   │
               │   ├─ Header → bailout (没有变化,跳过)
               │   │
               │   ├─ ProductList → updateFunctionComponent
               │   │   ├─ renderWithHooks(ProductList)
               │   │   │   ├─ 设置 Dispatcher = HooksDispatcherOnUpdate
               │   │   │   ├─ ProductList(props)
               │   │   │   │   ├─ useState([])            → Hook1
               │   │   │   │   ├─ useState('all')         → Hook2 ★
               │   │   │   │   │   ├─ updateState
               │   │   │   │   │   ├─ updateReducer
               │   │   │   │   │   ├─ 处理 queue.pending
               │   │   │   │   │   │   └─ Update { action: 'electronics' }
               │   │   │   │   │   ├─ 计算新状态
               │   │   │   │   │   │   └─ basicStateReducer('all', 'electronics')
               │   │   │   │   │   │       → 'electronics'
               │   │   │   │   │   └─ return ['electronics', setFilterCategory]
               │   │   │   │   ├─ useState('price')       → Hook3
               │   │   │   │   ├─ useState(false)         → Hook4
               │   │   │   │   └─ useEffect(fetchProducts, [filterCategory, sortBy]) → Hook5
               │   │   │   │       ├─ updateEffect
               │   │   │   │       ├─ 比较依赖:['electronics', 'price'] vs ['all', 'price']
               │   │   │   │       ├─ filterCategory 变化!
               │   │   │   │       └─ 标记 HookHasEffect
               │   │   │   └─ return JSX (CategoryFilter, SortOptions, ProductGrid)
               │   │   └─ reconcileChildren
               │   │       ├─ CategoryFilter → 新 props
               │   │       ├─ SortOptions → 无变化
               │   │       └─ ProductGrid → 新 props
               │   │
               │   ├─ CategoryFilter → updateFunctionComponent
               │   │   ├─ 检测到 props 变化 (selected: 'all' → 'electronics')
               │   │   ├─ renderWithHooks
               │   │   └─ reconcileChildren → FilterButton
               │   │       └─ className: '' → 'active' (标记 Update)
               │   │
               │   └─ ... (其他组件)
               │
               └─ completeWork(workInProgress)
                   ├─ HostComponent (FilterButton)
                   │   ├─ 对比 props
                   │   ├─ 计算 updatePayload
                   │   │   └─ ['className', 'active']
                   │   └─ workInProgress.updateQueue = updatePayload
                   │
                   └─ 标记 flags
                       └─ workInProgress.flags |= Update
    ↓
【所有 Fiber 节点处理完成】
workInProgress = null
    ↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 4: Commit 阶段(同步,不可中断)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
commitRoot(root)
    ↓
commitRootImpl(root)
    │
    ├─ Before Mutation 阶段
    │   ├─ commitBeforeMutationEffects
    │   ├─ 调用 getSnapshotBeforeUpdate(类组件)
    │   └─ 调度 useEffect(异步执行)
    │
    ├─ Mutation 阶段  DOM 更新发生
    │   ├─ commitMutationEffects
    │   ├─ 遍历 Fiber 树
    │   └─ FilterButton Fiber
    │       ├─ flags & Update
    │       ├─ commitUpdate(domElement, ['className', 'active'])
    │       └─ updateProperties
    │           └─ domElement.className = 'active'
    │               ↓
    │           【DOM 更新完成】
    │
    ├─ 切换 current 树
    │   └─ root.current = finishedWork
    │
    └─ Layout 阶段
        ├─ commitLayoutEffects
        ├─ 执行 useLayoutEffect
        ├─ 调用 componentDidMount/Update
        └─ 更新 ref.current
    ↓
【Commit 完成】
    ↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 5: 浏览器渲染
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
浏览器重绘
    ↓
【用户看到界面更新】✓
按钮的 className 从 '' 变为 'active'
    ↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 6: 异步执行 useEffect
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
【浏览器空闲时】
    ↓
flushPassiveEffects()
    ├─ commitPassiveUnmountEffects
    │   └─ 执行上一次 effect 的清理函数(如果有)
    └─ commitPassiveMountEffects
        └─ ProductList 的 useEffect
            ├─ 检查 tag & HookHasEffect
            ├─ 执行 effect 函数
            │   └─ fetchProducts()
            │       ├─ setLoading(true)     → 触发新的更新
            │       ├─ await api.getProducts(...)
            │       ├─ setProducts(data)    → 触发新的更新
            │       └─ setLoading(false)    → 触发新的更新
            └─ 保存 destroy 函数
    ↓
【新的更新循环开始】
从 Phase 1 重新开始...

9.2 关键时间点

时间点 事件 用户可见性 耗时
T0 用户点击按钮 - 0ms
T1 setState 调用完成 - <1ms
T2 Scheduler 调度完成 - <1ms
T3 Render 阶段开始 - 0-5ms
T4 Render 阶段完成 - (可能被中断)
T5 Commit - Mutation 界面更新 1-3ms
T6 Commit - Layout - <1ms
T7 浏览器重绘 用户看到变化 ~16ms
T8 useEffect 执行 - (异步)

总时间(T0 → T7):通常 10-30ms(用户无感知)


总结

setState 完整流程总结

  1. 调用 setState:创建 Update 对象,加入队列,调度更新
  2. 计算优先级:使用 Lane 模型,根据事件类型确定优先级
  3. Scheduler 调度:根据优先级,将渲染任务加入任务队列
  4. Render 阶段
    • beginWork:处理组件,执行 Hooks,计算新状态
    • completeWork:创建/更新 DOM 节点(不插入页面)
    • 使用 Diff 算法最小化 DOM 操作
  5. Commit 阶段
    • Before Mutation:执行 getSnapshotBeforeUpdate
    • Mutation:真正的 DOM 更新(用户看到变化)
    • Layout:执行 useLayoutEffect、componentDidMount/Update
  6. 异步执行 useEffect:在浏览器空闲时执行

关键优化点

  1. 批量更新:多次 setState 只触发一次渲染
  2. 预计算优化:如果新旧状态相同,跳过更新
  3. Bailout 机制:组件 props 未变化,跳过渲染
  4. 时间切片:Render 阶段可中断,不阻塞用户交互
  5. 优先级调度:高优先级任务优先处理
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐