Appearance
第 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 的决策逻辑非常清晰,它会依次检查几个条件,以确定更新的来源和性质:
- 是否在 Transition 中? 如果当前更新是通过
startTransitionAPI 发起的(例如,在一个useTransition的回调中),那么它会被视为一个“过渡性”更新,并被分配一个低优先级的TransitionLane。 - 否则,根据事件类型确定优先级。 对于非过渡性更新,React 会调用
getCurrentEventPriority()来检查当前执行上下文。这个函数会判断更新是由哪种类型的“事件”触发的:- 离散事件 (Discrete Event):如
click,keydown,focus。这些事件由用户的直接、明确的动作触发,期望得到立即反馈。它们会被赋予最高的优先级,并映射到SyncLane。 - 连续事件 (Continuous Event):如
mousemove,scroll,drag。这些事件会频繁触发,如果每次都立即处理,可能导致性能问题。它们会被赋予中等优先级,并映射到InputContinuousLane。 - 默认事件 (Default Event):不属于上述两类的其他事件或更新(例如,由
setTimeout回调触发的更新)会被赋予普通优先级,并映射到DefaultLane。
- 离散事件 (Discrete Event):如
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 能够在更新被创建的最初阶段,就为其赋予一个合适的“紧急程度”标签,从而在整个并发调度流程中,确保最重要的任务总是能够被优先、及时地执行。