Skip to content

React 更新机制详解

在 React 中,任何能够引起 UI 变化的操作都被视为一次"更新"。从用户交互(如点击按钮)到 API 调用(如 setState),这些触发源最终都会被转化为一个标准的内部更新对象,并进入调度流程。本章节将详细拆解从更新触发到任务调度的全过程,揭示其如何与调度阶段(Scheduling Phase)紧密相连。

一、更新的触发源

React 的更新可以由多种事件触发,它们共同构成了驱动应用动态变化的基础。这些触发源可以大致分为以下几类:

1. 初始渲染

typescript
// React 19.2.0 中的初始渲染
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root')!);
root.render(<App />); // 触发初始更新

2. 状态与 Props 变更

typescript
interface CounterState {
  count: number;
}

const [count, setCount] = useState<number>(0);

// 同步更新
setCount(count + 1);

// 函数式更新(推荐)
setCount(prev => prev + 1);

// 批量更新(React 18+)
setTimeout(() => {
  setCount(c => c + 1); // 自动批处理
  setCount(c => c + 1);
}, 1000);

Props 变化

typescript
interface ChildProps {
  value: number;
  onUpdate: (value: number) => void;
}

// 父组件重新渲染导致子组件 props 变化
function Parent() {
  const [state, setState] = useState(0);
  
  return (
    <ChildComponent 
      value={state} 
      onUpdate={setState}
    />
  );
}

Context 变化

typescript
interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | null>(null);

// Context Provider 值变化触发消费者更新
function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  
  const toggleTheme = useCallback(() => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  }, []);
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

3. React 19 新机制

Actions(异步状态管理)

typescript
interface FormData {
  name: string;
  email: string;
}

function MyComponent() {
  const [isPending, startTransition] = useTransition();
  const [error, setError] = useState<string | null>(null);
  
  const handleSubmit = async (formData: FormData) => {
    try {
      startTransition(async () => {
        const result = await submitForm(formData);
        if (!result.success) {
          setError(result.error);
        }
      });
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
    }
  };
  
  return (
    <form action={handleSubmit}>
      {isPending && <div>Submitting...</div>}
      {error && <div>Error: {error}</div>}
      {/* form fields */}
    </form>
  );
}

use(Promise)(Suspense 集成)

typescript
interface ApiResponse {
  data: any[];
  status: 'success' | 'error';
}

function DataComponent({ promise }: { promise: Promise<ApiResponse> }) {
  try {
    const result = use(promise); // Promise resolve 时触发更新
    
    if (result.status === 'error') {
      throw new Error('Failed to load data');
    }
    
    return (
      <div>
        {result.data.map(item => (
          <div key={item.id}>{item.name}</div>
        ))}
      </div>
    );
  } catch (error) {
    // 错误边界会捕获这个错误
    throw error;
  }
}

Server Components(服务端渲染优化)

typescript
// 服务端组件(在服务器上运行)
async function ServerComponent() {
  try {
    const data = await fetchData();
    return <ClientComponent data={data} />;
  } catch (error) {
    console.error('Server component error:', error);
    return <ErrorFallback />;
  }
}

// 客户端组件
'use client';
function ClientComponent({ data }: { data: any[] }) {
  const [localState, setLocalState] = useState(data);
  
  useEffect(() => {
    // 客户端特定逻辑
    setLocalState(data);
  }, [data]);
  
  return (
    <div>
      {localState.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}

4. 强制同步更新

typescript
// flushSync:强制同步执行更新
import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() => {
    setCount(c => c + 1);
  });
  // 此时 DOM 已经同步更新
  console.log(document.getElementById('counter')?.textContent);
}

尽管触发源多种多样,但它们最终都会殊途同归,进入 React 内部统一的更新处理流程。

二、更新的创建与入队

无论更新源自何处,React 都会执行标准化的内部流程:创建 Update 对象 -> 计算优先级(Lane)-> 将 Update 入队

1. 创建 Update 对象

typescript
// React 19.2.0 中的 Update 类型定义
interface Update<State> {
  eventTime: number;
  lane: Lane;
  tag: UpdateTag;
  payload: any;
  callback: (() => void) | null;
  next: Update<State> | null;
}

enum UpdateTag {
  UpdateState = 0,
  ReplaceState = 1,
  ForceUpdate = 2,
  CaptureUpdate = 3,
}

function createUpdate<State>(eventTime: number, lane: Lane): Update<State> {
  const update: Update<State> = {
    eventTime,
    lane,
    tag: UpdateTag.UpdateState,
    payload: null,
    callback: null,
    next: null,
  };
  
  if (__DEV__) {
    // 开发环境下的调试信息
    (update as any)._debugSource = new Error().stack;
  }
  
  return update;
}

2. Lane 的计算(优先级系统)

typescript
// Lane 类型定义
type Lane = number;
type Lanes = number;

// 优先级常量
const SyncLane: Lane = 0b0000000000000000000000000000001;
const InputContinuousLane: Lane = 0b0000000000000000000000000000100;
const DefaultLane: Lane = 0b0000000000000000000000000010000;
const TransitionLane1: Lane = 0b0000000000000000000000001000000;

function requestUpdateLane(fiber: Fiber): Lane {
  const mode = fiber.mode;
  
  // 非并发模式直接返回同步 Lane
  if ((mode & ConcurrentMode) === NoMode) {
    return SyncLane;
  }
  
  // 检查是否在 Transition 中
  if (isTransition()) {
    const transition = getCurrentTransition();
    if (transition !== null) {
      return claimNextTransitionLane();
    }
  }
  
  // 根据当前事件优先级计算 Lane
  const eventPriority = getCurrentEventPriority();
  
  switch (eventPriority) {
    case DiscreteEventPriority:
      return SyncLane;
    case ContinuousEventPriority:
      return InputContinuousLane;
    case DefaultEventPriority:
    default:
      return DefaultLane;
  }
}

// 性能监控:Lane 使用统计
if (__DEV__) {
  let laneUsageStats: Record<string, number> = {};
  
  function trackLaneUsage(lane: Lane) {
    const laneName = getLaneName(lane);
    laneUsageStats[laneName] = (laneUsageStats[laneName] || 0) + 1;
  }
  
  function getLaneName(lane: Lane): string {
    if (lane === SyncLane) return 'SyncLane';
    if (lane === InputContinuousLane) return 'InputContinuousLane';
    if (lane === DefaultLane) return 'DefaultLane';
    return `TransitionLane_${lane.toString(2)}`;
  }
}

3. 入队 Update(环形链表结构)

typescript
interface UpdateQueue<State> {
  baseState: State;
  firstBaseUpdate: Update<State> | null;
  lastBaseUpdate: Update<State> | null;
  shared: SharedQueue<State>;
  effects: Array<Update<State>> | null;
}

interface SharedQueue<State> {
  pending: Update<State> | null;
  interleaved: Update<State> | null;
  lanes: Lanes;
}

function enqueueUpdate<State>(
  fiber: Fiber,
  update: Update<State>,
  lane: Lane
): void {
  const updateQueue = fiber.updateQueue as UpdateQueue<State>;
  
  if (updateQueue === null) {
    // 组件已卸载
    if (__DEV__) {
      console.warn('Cannot update unmounted component');
    }
    return;
  }
  
  const sharedQueue = updateQueue.shared;
  
  // 标记 Lane
  markUpdateLaneFromFiberToRoot(fiber, lane);
  
  // 构建环形链表
  const pending = sharedQueue.pending;
  if (pending === null) {
    // 第一个更新,指向自己形成环
    update.next = update;
  } else {
    // 插入到环形链表中
    update.next = pending.next;
    pending.next = update;
  }
  
  // 更新 pending 指针
  sharedQueue.pending = update;
  
  // 性能监控
  if (__DEV__) {
    trackLaneUsage(lane);
    measureUpdateQueueLength(updateQueue);
  }
}

// 调试工具:测量更新队列长度
function measureUpdateQueueLength<State>(updateQueue: UpdateQueue<State>): number {
  const pending = updateQueue.shared.pending;
  if (pending === null) return 0;
  
  let count = 1;
  let current = pending.next;
  while (current !== pending) {
    count++;
    current = current!.next;
  }
  
  if (__DEV__ && count > 10) {
    console.warn(`Large update queue detected: ${count} updates pending`);
  }
  
  return count;
}

这个过程确保了所有更新都被规范化,并准备好进入下一步的调度环节。

三、调度入口:连接 Reconciler 与 Scheduler

更新入队后,需要触发调度。这个过程通过两个关键函数实现:

  1. scheduleUpdateOnFiber - Reconciler 的调度入口
  2. ensureRootIsScheduled - 连接到 Scheduler 的桥梁

这个流程体现了 React 架构的几个重要特点:

1. 职责分离

  • Reconciler 负责 Fiber 树的构建和 Diff 算法
  • Scheduler 负责任务的时间切片和优先级调度
  • scheduleUpdateOnFiberensureRootIsScheduled 作为连接层
  • Lane 系统 负责优先级管理和批处理

2. 关键桥梁

这两个函数是整个调度系统的关键桥梁:

  • 从应用层的状态更新(用户交互、数据变化)
  • 到底层的任务调度(时间切片、优先级队列)
  • 实现了优雅的解耦和可扩展性

3. 异步实现与性能优化

通过 Scheduler 实现了:

  • 时间切片(Time Slicing):将长任务分解为小块
  • 优先级调度(Priority Scheduling):高优先级任务优先执行
  • 可中断渲染(Interruptible Rendering):支持任务中断和恢复
  • 批处理优化(Batching):合并多个更新减少渲染次数
  • 并发特性(Concurrent Features):Suspense、Transitions 等

4. React 19 增强特性

  • 自动批处理:所有更新默认批处理,包括 setTimeout、Promise 等
  • Transition API:区分紧急和非紧急更新
  • Suspense 改进:更好的加载状态管理
  • Server Components:服务端渲染优化

四、scheduleUpdateOnFiber:调度的统一入口

scheduleUpdateOnFiber 是 React 调度系统的核心入口函数,负责处理 Fiber 节点上的更新调度。以下是移除开发代码后的核心实现和详细解析:

scheduleUpdateOnFiber 函数执行流程

  1. 标记更新:调用 markRootUpdated 更新根节点的车道信息

  2. 渲染阶段检查

    • 如果在渲染阶段收到更新,标记渲染阶段更新车道
    • 否则进入正常调度流程
  3. 挂起状态处理

    • 检查根节点是否因数据延迟而挂起
    • 如果是,调用 prepareFreshStack 中断当前渲染
  4. 调度确保:调用 ensureRootIsScheduled 确保根节点被调度

  5. 同步工作刷新

    • 在特定条件下(同步车道、无执行上下文、非并发模式等)
    • 立即刷新同步工作
scheduleUpdateOnFiber 内部函数解析
javascript
export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
) {
  // 标记根节点已更新
  markRootUpdated(root, lane);

  // 检查是否在渲染阶段
  if (
    (executionContext & RenderContext) !== NoLanes &&
    root === workInProgressRoot
  ) {
    // 在渲染阶段收到更新,标记渲染阶段更新车道
    workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
      workInProgressRootRenderPhaseUpdatedLanes,
      lane,
    );
  } else {
    // 检查根节点是否因延迟而挂起
    if (
      workInProgressRoot === root &&
      workInProgressRootExitStatus === RootSuspendedWithDelay &&
      workInProgressSuspendedReason === SuspendedOnData
    ) {
      // 中断当前渲染并切换到新更新
      prepareFreshStack(root, NoLanes);
    }

    // 确保根节点被调度
    ensureRootIsScheduled(root);

    // 如果满足条件,立即刷新同步工作
    if (
      lane === SyncLane &&
      executionContext === NoContext &&
      (fiber.mode & ConcurrentMode) === NoMode &&
      !ReactCurrentActQueue.isBatchingLegacy
    ) {
      resetRenderTimer();
      flushSyncWorkOnLegacyRootsOnly();
    }
  }
}

核心调用的重要函数解析

1. markRootUpdated(root, lane)

作用:标记根节点已更新,更新相关车道信息

javascript
export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
  // 将更新车道添加到待处理车道
  root.pendingLanes |= updateLane;
  
  // 标记可能需要显示加载指示器的车道
  if (enableDefaultTransitionIndicator) {
    root.indicatorLanes |= updateLane & TransitionLanes;
  }

  // 清除挂起的车道,因为新更新可能解除阻塞
  if (updateLane !== IdleLane) {
    root.suspendedLanes = NoLanes;
    root.pingedLanes = NoLanes;
    root.warmLanes = NoLanes;
  }
}

2. prepareFreshStack(root, lanes)

作用:准备新的工作栈,重置渲染状态

javascript
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
  // 取消之前的超时处理
  const timeoutHandle = root.timeoutHandle;
  if (timeoutHandle !== noTimeout) {
    root.timeoutHandle = noTimeout;
    cancelTimeout(timeoutHandle);
  }
  
  // 取消待处理的提交
  const cancelPendingCommit = root.cancelPendingCommit;
  if (cancelPendingCommit !== null) {
    root.cancelPendingCommit = null;
    cancelPendingCommit();
  }

  // 重置工作进度栈
  resetWorkInProgressStack();
  workInProgressRoot = root;
  
  // 创建新的工作进度节点
  const rootWorkInProgress = createWorkInProgress(root.current, null);
  workInProgress = rootWorkInProgress;
  
  // 重置所有工作进度相关状态
  workInProgressRootRenderLanes = lanes;
  workInProgressSuspendedReason = NotSuspended;
  workInProgressThrownValue = null;
  workInProgressRootDidSkipSuspendedSiblings = false;
  workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
  workInProgressRootDidAttachPingListener = false;
  workInProgressRootExitStatus = RootInProgress;
  workInProgressRootSkippedLanes = NoLanes;
  workInProgressRootInterleavedUpdatedLanes = NoLanes;
  workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
  workInProgressRootPingedLanes = NoLanes;
  workInProgressDeferredLane = NoLane;
  workInProgressSuspendedRetryLanes = NoLanes;
  workInProgressRootConcurrentErrors = null;
  workInProgressRootRecoverableErrors = null;
  workInProgressRootDidIncludeRecursiveRenderUpdate = false;

  // 获取纠缠的渲染车道
  entangledRenderLanes = getEntangledLanes(root, lanes);
  
  // 完成并发更新队列
  finishQueueingConcurrentUpdates();

  return rootWorkInProgress;
}

3. ensureRootIsScheduled(root)

作用:确保根节点被调度(详见之前的解析)

这个函数是调度系统的核心,负责:

  • 将根节点添加到调度队列
  • 确保有微任务处理根调度
  • 处理同步工作刷新

4. flushSyncWorkOnLegacyRootsOnly()

作用:仅在旧版根节点上刷新同步工作

javascript
function flushSyncWorkOnLegacyRootsOnly() {
  // 只处理旧版模式的根节点
  if (rootsWithPendingDiscreteUpdates !== null) {
    const roots = rootsWithPendingDiscreteUpdates;
    rootsWithPendingDiscreteUpdates = null;
    roots.forEach(root => {
      if ((root.mode & ConcurrentMode) === NoMode) {
        flushRoot(root, SyncLane);
      }
    });
  }
  flushSyncWorkAcrossRoots_impl(true);
}

五、ensureRootIsScheduled:调度阶段的统一入口

ensureRootIsScheduled 可以被视为 React 调度阶段的 最终统一入口 ,它负责将所有待处理的更新请求,以标准化的方式提交给底层的 Scheduler,从而启动或更新 React 的工作循环。这是 React 实现统一调度和优先级管理的关键所在。

ensureRootIsScheduled函数
javascript
function ensureRootIsScheduled(root: FiberRoot): void {
  // 1. 获取当前根节点的调度优先级 (Scheduler Priority)
  //    这个优先级是根据 root.pendingLanes 计算得出的,代表了当前根节点上所有待处理更新中最高的优先级。
  const currentSchedulerPriority = getHighestPriorityLane(root.pendingLanes)

  // 2. 如果当前根节点已经有一个调度任务,并且其优先级低于或等于新的调度优先级,
  //    则无需重新调度,直接返回。
  //    这避免了重复调度低优先级的任务。
  if (
    root.callbackNode !== null &&
    root.callbackPriority <= currentSchedulerPriority
  ) {
    return
  }

  // 3. 清除旧的调度任务(如果存在)
  //    如果需要重新调度(例如,有更高优先级的更新到来),则取消之前安排的调度任务。
  cancelCallback(root.callbackNode)

  // 4. 如果没有待处理的更新,则将 callbackNode 和 callbackPriority 重置为 null/NoLane,并返回。
  //    这意味着当前根节点没有需要处理的任务。
  if (currentSchedulerPriority === NoLane) {
    root.callbackNode = null
    root.callbackPriority = NoLane
    return
  }

  // 5. 根据 React 优先级计算出对应的调度器优先级 (Scheduler Priority)
  //    React 内部的 Lane 优先级需要映射到 Scheduler 模块理解的优先级。
  const schedulerPriority = reactPriorityToSchedulerPriority(
    currentSchedulerPriority,
  )

  // 6. 计算调度任务的过期时间 (Expiration Time)
  //    这决定了任务最迟何时必须执行,即使浏览器不空闲。
  const expirationTime = calculateExpirationTime(currentSchedulerPriority)

  // 7. 安排一个新的调度任务
  //    调用 Scheduler 模块的 `scheduleCallback` 函数,安排一个回调函数。
  //    这个回调函数通常是 `performSyncWorkOnRoot` 或 `performConcurrentWorkOnRoot`,
  //    它将在浏览器空闲时或过期时间到达时执行,从而启动协调阶段。
  root.callbackNode = scheduleCallback(
    schedulerPriority,
    performWorkOnRoot.bind(null, root),
    { timeout: expirationTime },
  )
  root.callbackPriority = currentSchedulerPriority
}

// 辅助函数(在实际代码中可能位于其他文件或作为内部实现)
function getHighestPriorityLane(lanes: Lanes): Lane {
  /* ... */
}
function cancelCallback(callbackNode: CallbackNode): void {
  /* ... */
}
function reactPriorityToSchedulerPriority(
  reactPriority: Lane,
): SchedulerPriority {
  /* ... */
}
function calculateExpirationTime(priority: Lane): Milliseconds {
  /* ... */
}
function scheduleCallback(
  priority: SchedulerPriority,
  callback: Function,
  options: { timeout: Milliseconds },
): CallbackNode {
  /* ... */
}
function performWorkOnRoot(
  root: FiberRoot,
  didTimeout: boolean,
): CallbackNode | null {
  /* ... */
}

解析

packages/react-reconciler/src/ReactFiberRootScheduler.js 中的 ensureRootIsScheduled 函数是 React 调度阶段的核心入口之一,它的主要职责是根据当前 Fiber 根节点(FiberRoot)上待处理的更新(pendingLanes),决定是否需要向 Scheduler 模块安排一个新的调度任务,以及如何安排这个任务。

  1. 获取当前根节点的调度优先级 (currentSchedulerPriority)

    • getHighestPriorityLane(root.pendingLanes):这个函数会从 root.pendingLanes(一个位掩码,表示所有待处理更新的优先级集合)中,找出优先级最高的那个 Lane。这个 Lane 代表了当前根节点上最紧急的更新。
    • 目的:确定当前根节点需要处理的最高优先级任务是什么。
  2. 避免重复调度

    • if (root.callbackNode !== null && root.callbackPriority <= currentSchedulerPriority):这里进行了一个重要的优化检查。如果当前根节点已经有一个调度任务(root.callbackNode 不为 null),并且这个已存在的任务的优先级(root.callbackPriority)已经足够高(即小于或等于 currentSchedulerPriority),那么就没有必要再安排一个新的调度任务了。直接返回,避免不必要的 Scheduler 调用。
    • 目的:防止重复调度,提高效率。
  3. 清除旧的调度任务

    • cancelCallback(root.callbackNode):如果上述条件不满足(即没有调度任务,或者新来的更新优先级更高),那么就需要重新安排调度。在安排新任务之前,必须取消掉之前可能存在的旧的调度任务。这是因为新的任务可能具有更高的优先级,或者旧的任务已经不再需要。
    • 目的:确保只有一个有效的调度任务在 Scheduler 中排队,并且总是最高优先级的任务。
  4. 处理无更新情况

    • if (currentSchedulerPriority === NoLane):如果 getHighestPriorityLane 返回 NoLane,表示当前根节点上没有任何待处理的更新。在这种情况下,将 root.callbackNoderoot.callbackPriority 重置为 nullNoLane,并直接返回。
    • 目的:当没有更新时,不进行任何调度。
  5. 优先级转换

    • const schedulerPriority = reactPriorityToSchedulerPriority(currentSchedulerPriority):React 内部使用 Lane 模型来管理优先级,而 Scheduler 模块有自己的一套优先级系统(例如 ImmediatePriority, UserBlockingPriority, NormalPriority, LowPriority, IdlePriority)。这个函数负责将 React 的 Lane 优先级映射到 Scheduler 对应的优先级。
    • 目的:将 React 内部的优先级概念转换为 Scheduler 可以理解和处理的优先级。
  6. 计算过期时间 (expirationTime)

    • const expirationTime = calculateExpirationTime(currentSchedulerPriority):对于某些优先级(特别是同步或用户阻塞的优先级),React 会计算一个“过期时间”。这意味着即使浏览器主线程一直很忙,Scheduler 也必须在这个时间点之前强制执行这个任务。这保证了高优先级任务的响应性。
    • 目的:为调度任务设置一个截止时间,确保高优先级任务不会无限期延迟。
  7. 安排新的调度任务

    • root.callbackNode = scheduleCallback(...):这是与 Scheduler 模块交互的关键步骤。scheduleCallback 函数会向 Scheduler 注册一个回调函数(通常是 performWorkOnRoot),并传入计算出的 schedulerPriorityexpirationTime
      • performWorkOnRoot.bind(null, root):这个回调函数是真正启动 React 协调阶段的入口。当 Scheduler 认为时机合适时(例如,浏览器有空闲时间,或者任务过期),它会调用这个函数,并传入 root 对象。
      • { timeout: expirationTime }:将过期时间传递给 Scheduler,以便它可以在必要时强制执行任务。
    • root.callbackPriority = currentSchedulerPriority:更新 root.callbackPriority,记录当前安排的调度任务的优先级。
    • 目的:将 React 的更新任务提交给底层的调度器,由调度器在合适的时机执行,从而启动 Render Phase。

scheduleUpdateOnFiber 通过 ensureRootIsScheduled 将工作单元“注册”到调度系统中,然后调度器在认为合适的时机“唤醒”这个工作单元,从而启动渲染的准备和执行流程。而这马上到第二个阶段。

全流程

为了更直观地理解从更新触发到任务调度的完整链路,我们可以通过以下流程图来展示:

从流程图中我们可以看到:

  • 职责分离Reconciler 负责 "什么需要更新"(创建 Update、管理 Fiber),而 Scheduler 负责 "何时执行更新"(管理任务队列、与浏览器协作)。
  • 关键桥梁ensureRootIsScheduledunstable_scheduleCallback 是连接这两个模块的核心接口。
  • 异步实现:通过 MessageChannel 将调度任务推入宏任务队列,Scheduler 实现了不阻塞主线程的异步执行,这是 React 并发模式的基石。

微信公众号二维码