在 React 18(以及 React 17 的重构版本)中,优先级的概念被具象化为 "Lanes"(车道)模型。
React 内部使用一套复杂的**位运算(Bitmask)**来管理这些优先级。为了方便理解,我们可以将它们映射为 Scheduler(调度器) 的 5 个核心等级。
理解这些优先级,是你掌握 React 并发机制(Concurrent Features)和性能调优的钥匙。
以下是按照优先级从高到低的详细分类:
1. Immediate / Synchronous Priority (立即/同步优先级)
对应的 Lane: SyncLane 权重: 最高级(No.1) 不可中断
- 定义: 必须立即完成的任务。如果不立即更新,用户会觉得"坏了"或"没反应"。这种更新会绕过 React 的时间切片机制,强制同步执行(类似于 React 15 的行为)。
- 触发场景:
- 离散的用户交互:
click(点击),input(输入),keydown(按键),touchstart。 - 强制同步 API:
flushSync(() => { ... })。
- 离散的用户交互:
- 行为:
- 一旦触发,React 会锁死主线程,直到 DOM 更新完成。
- 如果在
click事件中写了死循环,浏览器会直接卡死。
- 过期时间: -1ms(立刻过期,必须马上做)。
2. User Blocking Priority (用户阻塞优先级)
对应的 Lane: InputContinuousLane 权重: 次高级(No.2) 短时间可中断,但必须尽快
- 定义: 同样是用户交互,但属于连续触发的类型。虽然需要高频响应,但允许极短的延迟(为了防止连续触发导致 JS 线程完全堵塞)。
- 触发场景:
scroll(滚动)mousemove(鼠标移动)drag(拖拽)wheel(滚轮)
- 行为:
- React 会尝试尽快处理,但如果短时间内触发太多,React 会进行一定的合并(Batching)。
- 它比点击事件低一级,是因为:用户滚动时,如果画面有一丢丢(比如几毫秒)的跟手延迟,通常是可以接受的,但点击按钮没反应是不能接受的。
- 过期时间: 250ms(如果排队超过 250ms 还没执行,就会强制升级为同步任务立即执行)。
3. Normal Priority (普通/默认优先级)
对应的 Lane: DefaultLane 权重: 中级(No.3) 可中断
- 定义: React 中的默认优先级。如果你没有特殊操作,绝大多数状态更新都落在这个桶里。
- 触发场景:
useEffect里发起的更新。- 网络请求(
fetch,axios)回调里的setState。 setTimeout/setInterval里的更新。- 自定义事件监听器。
- 行为:
- 并发模式的主战场: 这里的任务会利用 Time Slicing(时间切片)。
- React 会给每个任务分配 5ms 的时间片。如果 5ms 没做完,就暂停,把主线程还给浏览器去画帧,下一帧再继续。
- 过期时间: 5000ms(5秒)。这意味着即使很卡,5秒后 React 也会强制把这个任务执行完,保证界面最终会更新。
4. Low Priority (低优先级)
对应的 Lane: TransitionLane (React 18 新增) 权重: 低级(No.4) 随时可被中断、可被废弃
- 定义: 明确告诉 React "我不着急"的任务。这是 React 18 并发特性的核心。
- 触发场景:
- 被
startTransition包裹的更新。 useDeferredValue产生的更新。Suspense的重试机制。
- 被
- 行为:
- "备胎": 只要有更高优先级的任务(比如用户点了一下鼠标),正在运行的 Low Priority 任务就会立刻被暂停,甚至直接扔掉。
- 无 Loading: 在 Transition 期间,React 会保留旧的 UI,在后台悄悄计算新 UI,算好了再一次性切换。
- 典型用例: 搜索框下方的长列表过滤、Tab 切换时的复杂页面渲染。
5. Idle Priority (空闲优先级)
对应的 Lane: IdleLane 权重: 最低级(No.5)
- 定义: 只有当浏览器完全没事干(主线程空闲)时才会执行的任务。
- 触发场景:
- 通常用于 Offscreen(离屏) 内容的预渲染。
- 比如:隐藏的 Tab 页、视口之外的组件预热。
- 行为:
- 优先级无限低,可能永远不会被执行(如果一直忙的话)。
- React 内部其实还没大规模暴露这个给开发者直接用,主要在框架内部使用(如
BeforeContent,OffscreenComponent)。
总结图谱
为了方便记忆,你可以想象成医院的急诊分诊台:
| 优先级名称 | 对应交互 | 医院比喻 | 行为特点 |
|---|---|---|---|
| Immediate | click, input, flushSync |
心脏骤停 (抢救) | 医生放下所有事,立刻处理,不能等。 |
| User Blocking | scroll, drag |
骨折/外伤 (急诊) | 很痛,要尽快看,但稍微排几分钟队死不了人。 |
| Normal | fetch, setTimeout |
发烧/感冒 (普通门诊) | 正常排队,医生看一会儿可能会去接个急诊电话。 |
| Low | startTransition |
常规体检 | 随时可以被插队,今天人多就明天再来。 |
| Idle | Offscreen | 医院保洁 | 没病人的时候打扫卫生。 |
为什么你要知道这些?
- 性能调试: 当你发现输入框打字卡顿时,你要反应过来:是不是混入了大量的计算逻辑?是不是应该把列表过滤的计算降级为
Low Priority(使用useDeferredValue)? - 避免滥用: 以前为了优化,很多开发者喜欢用
setTimeout(..., 0)来把任务往后排。现在你知道了,setTimeout属于 Normal 优先级,而startTransition属于更低的 Low 优先级,后者在 React 18 中通常是更好的选择,因为它支持中断。 - 理解
batchedUpdates: 只有同一优先级的更新才会被合并。高优先级会打断低优先级的合并。