Skip to content

第 9 章第 3 节:状态更新的优先级

在并发模式下,React 面临的核心挑战之一是如何处理同时发生的多个状态更新。并非所有更新都同等重要:用户点击按钮的反馈必须立即响应,而数据加载后更新列表则可以稍缓。为了解决这个问题,React 引入了一套基于“优先级”和“泳道”(Lanes)的模型,为每个更新分配一个合适的处理顺序。

1. requestUpdateLane:分配优先级的起点

当一个状态更新(如 setState)被触发时,React 需要立即为其分配一个优先级。这个决策的起点是 requestUpdateLane 函数。

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

function requestUpdateLane(fiber: Fiber): Lane {
  // 1. 是否在 Transition 中?
  const transition = requestCurrentTransition();
  if (transition !== null) {
    return requestTransitionLane(transition);
  }

  // 2. 根据当前的执行上下文获取事件优先级
  const eventPriority = getCurrentEventPriority();

  // 3. 将事件优先级映射到具体的 Lane
  return eventPriorityToLane(eventPriority);
}

requestUpdateLane 的决策逻辑非常清晰,它会依次检查几个条件,以确定更新的来源和性质:

  1. 是否在 Transition 中? 如果当前更新是通过 startTransition API 发起的(例如,在一个 useTransition 的回调中),那么它会被视为一个“过渡性”更新,并被分配一个低优先级的 TransitionLane
  2. 否则,根据事件类型确定优先级。 对于非过渡性更新,React 会调用 getCurrentEventPriority() 来检查当前执行上下文。这个函数会判断更新是由哪种类型的“事件”触发的:
    • 离散事件 (Discrete Event):如 click, keydown, focus。这些事件由用户的直接、明确的动作触发,期望得到立即反馈。它们会被赋予最高的优先级,并映射到 SyncLane
    • 连续事件 (Continuous Event):如 mousemove, scroll, drag。这些事件会频繁触发,如果每次都立即处理,可能导致性能问题。它们会被赋予中等优先级,并映射到 InputContinuousLane
    • 默认事件 (Default Event):不属于上述两类的其他事件或更新(例如,由 setTimeout 回调触发的更新)会被赋予普通优先级,并映射到 DefaultLane

2. 从事件到泳道:Lane 的映射

eventPriorityToLane 函数是连接“事件优先级”和“内部泳道”的桥梁。

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

function eventPriorityToLane(eventPriority: EventPriority): Lane {
  switch (eventPriority) {
    case DiscreteEventPriority: // click, keydown
      return SyncLane;
    case ContinuousEventPriority: // scroll, mousemove
      return InputContinuousLane;
    case DefaultEventPriority: // 其他情况
    default:
      return DefaultLane;
  }
}

通过这个映射,React 将开发者熟悉的“事件类型”概念,转换为了内部用于调度的“泳道”(Lane)概念。每个 Lane 本质上是一个位掩码(Bitmask),这使得 React 可以非常高效地通过位运算来管理、合并和比较不同更新的优先级。

3. 案例:setState 的优先级之旅

让我们通过一个具体的例子来理解这个过程:

jsx
function MyComponent() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    // 这是一个离散事件,setCount 会被赋予 SyncLane 优先级
    setCount(c => c + 1);
  };

  const handleScroll = () => {
    // 这是一个连续事件,setCount 会被赋予 InputContinuousLane 优先级
    setCount(c => c + 1);
  };

  const handleTimeout = () => {
    setTimeout(() => {
      // 这是一个默认事件,setCount 会被赋予 DefaultLane 优先级
      setCount(c => c + 1);
    }, 1000);
  };

  const handleTransition = () => {
    // 这是一个过渡性更新,setCount 会被赋予 TransitionLane 优先级
    startTransition(() => {
      setCount(c => c + 1);
    });
  };

  return (
    <div>
      <button onClick={handleClick}>Click (Sync)</button>
      <div onScroll={handleScroll} style={{overflow: 'scroll', height: '100px'}}>Scroll (Continuous)</div>
      <button onClick={handleTimeout}>Timeout (Default)</button>
      <button onClick={handleTransition}>Transition</button>
      <p>Count: {count}</p>
    </div>
  );
}

在这个例子中,同一个 setCount 函数,由于被不同的“事件”触发,会被 requestUpdateLane 赋予完全不同的优先级。当这些更新同时存在于调度队列中时,React 会优先处理由 handleClick 触发的 SyncLane 更新,而将由 handleTransition 触发的 TransitionLane 更新推迟处理。

通过 requestUpdateLane 这套机制,React 能够在更新被创建的最初阶段,就为其赋予一个合适的“紧急程度”标签,从而在整个并发调度流程中,确保最重要的任务总是能够被优先、及时地执行。

Last updated: