在 React 的并发渲染体系中,优先级机制是支撑“可中断渲染”和“时间切片”的核心基础设施。React 需要知道哪些更新更紧急、哪些可以稍后处理,才能在有限的浏览器空闲时间内做出正确的调度决策。这套机制由 Lane 模型 实现。
为什么需要优先级
浏览器的主线程既要执行 JavaScript、又要处理用户交互、还要完成布局和绘制。如果一段长时间的同步渲染阻塞了主线程,用户点击按钮、输入文字、滚动页面时就会感到明显的卡顿。
React 解决这个问题的思路是:将更新划分为不同的优先级,紧急的更新(如用户输入)优先处理并尽快完成,非紧急的更新(如列表加载更多数据)可以拆分成小块、在空闲时段逐步完成,必要时还能被更高优先级的任务中断。
优先级机制让 React 能够在“保证界面响应”和“完成渲染任务”之间取得平衡。
Lane 模型的设计
React 使用 Lane(车道)来表示优先级,它是一个 31 位的二进制位掩码。每一位代表一个特定的优先级车道,数值越小通常优先级越高(有例外,如 SyncLane 为最高)。多个优先级可以同时存在,通过位运算可以高效地进行合并、判断和移除。
// 一些核心 Lane 的定义(概念性示例)
SyncLane: 0b0000000000000000000000000000001 // 同步优先级,最高
InputContinuousLane: 0b0000000000000000000000000000100 // 连续输入
DefaultLane: 0b0000000000000000000000000010000 // 默认优先级
TransitionLane: 0b0000000000000000000001000000000 // Transition 优先级
IdleLane: 0b0100000000000000000000000000000 // 空闲优先级,最低
Lane 模型有几个关键优势:
- 批量操作高效:可以用位运算一次性为多个更新分配或合并优先级。
- 优先级继承:高优先级 Lane 可以包含低优先级 Lane,便于实现“挂起”逻辑。
- 灵活的抢占与等待:可以标记哪些车道正在工作中、哪些需要等待。
常见的优先级分类
React 内部定义了多种优先级类别,它们大致对应不同的用户交互场景:
| 优先级类别 | 触发场景 | 特点 |
|------------|----------|------|
| 同步优先级(SyncLane) | 由 ReactDOM.flushSync 强制同步渲染、或遗留模式下的生命周期 | 立即同步执行,不可中断,尽量少用 |
| 连续输入优先级(InputContinuousLane) | 用户键盘输入、鼠标拖拽等需要即时反馈的连续交互 | 高优先级,但可被更高优先级打断 |
| 默认优先级(DefaultLane) | 大多数普通状态更新(setState、useReducer) | 普通优先级,并发模式下会通过时间切片执行 |
| Transition 优先级(TransitionLane) | startTransition 或 useTransition 标记的更新 | 低优先级,可被任意其他更新打断,适用于非紧急 UI 转换 |
| 空闲优先级(IdleLane) | 隐藏内容、离屏渲染、数据预加载等“不着急”的任务 | 最低优先级,仅在浏览器完全空闲时执行 |
优先级如何在开发中使用
理解优先级不仅有助于调试,还能直接指导你写出更流畅的用户体验:
1. 使用 useTransition 或 startTransition 降低非紧急更新的优先级
当用户在一个搜索框中输入文字,你同时需要更新输入框的值(紧急)和展示过滤后的列表(非紧急)。直接 setState 会让两个更新处于相同的默认优先级,如果列表渲染很耗时,就可能卡住输入框。
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
// 紧急更新:立即显示用户输入
setQuery(e.target.value);
// 非紧急更新:用 Transition 降低优先级
startTransition(() => {
setResults(filterData(e.target.value));
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <div>过滤中...</div>}
<ResultList data={results} />
</div>
);
}
这样,即使用户快速连续输入,前面的过滤计算可以被及时中断,只保留最后一次的结果渲染,保证了输入的流畅。
2. 避免滥用 flushSync
flushSync 会强制进行同步渲染,打破 React 的批处理和优先级调度。除非你确实需要立即读取更新后的 DOM(例如测量元素尺寸或动画编排),否则不应使用。滥用会导致性能下降和失去并发模式的优点。
3. 通过 React DevTools Profiler 观察优先级
在开发环境中,你可以通过 React DevTools 的 Profiler 面板查看每次更新的触发原因和优先级。火焰图中标记为黄色的“被中断”渲染,通常就是你使用了 Transition 的结果,直观展示了优先级调度的工作。
优先级机制的内部简化流程
- 更新创建:当调用
setState时,React 根据产生更新的上下文(事件处理器、Transition、Effect 等)分配一个 Lane。 - 请求调度:React 调度器(Scheduler)收到一个任务(Task),其优先级根据 Lane 换算而来。
- 任务执行:调度器在浏览器空闲时执行任务。如果在执行过程中有更高优先级的任务进来,当前任务会被标记为“需要重新调度”并放弃主线程。
- 重新渲染:高优先级任务完成后,被中断的低优先级任务从上次中断的 Fiber 节点重新开始,这就是“可恢复的渲染”。
Lane 模型和优先级调度是 React 18 并发模式能够丝滑运行的基石,它让 React 应用第一次真正做到了“不阻塞用户交互”的渲染体验。