Lanes(车道优先级模型)
React 18 引入的优先级模型,用位掩码(bitmask)表示更新的优先级级别。替代了 React 16/17 的 expirationTime 模型,支持多个不同优先级的更新并行处理和批量合并。
核心概念
Lanes 是 React 18 的更新优先级模型。每个更新被分配一个 lane(车道),lane 用位掩码表示——一个 32 位整数中的某一位或某几位。不同的 lane 代表不同的优先级,调度器每次从待处理的 lanes 集合中挑选最高优先级的子集来处理。
SyncLane = 0b0000000000000000000000000000010 // 最高优先级
InputContinuousLane = 0b0000000000000000000000000001000 // 连续输入(拖拽等)
DefaultLane = 0b0000000000000000000000000100000 // 默认(setTimeout 等)
TransitionLane1 = 0b0000000000000000000001000000000 // startTransition 包裹的更新
IdleLane = 0b0100000000000000000000000000000 // 空闲时处理
为什么用位掩码而不是简单的数字优先级? 位运算天然支持集合操作:lanes & SyncLane 判断是否包含同步更新,lanes | TransitionLane 合并优先级,lanes & ~processedLanes 排除已处理的优先级——这些操作都是单条 CPU 指令,极其高效。
与 expirationTime 的对比
React 16/17 用 expirationTime(过期时间)表示优先级——数值越大优先级越高。这个模型的问题是无法表示"同时处理多个不连续的优先级级别"。例如,想同时处理 SyncLane 和某个 TransitionLane 但跳过 DefaultLane——用连续的时间范围无法表达这种"跳跃选择",但位掩码天然支持。
在调度流程中的位置
setState() → 分配 lane(基于触发上下文)
→ markUpdateLaneFromFiberToRoot(沿 Fiber 树向上标记 lanes)
→ ensureRootIsScheduled(调度器入口)
→ 选择本次要处理的 lanes 子集(getNextLanes)
→ 选择 workLoopSync 或 workLoopConcurrent
→ render 阶段只处理选中的 lanes 对应的更新
lane 的分配规则:
- 离散事件(click、keydown)→
SyncLane(最高优先级,同步执行) - 连续事件(mousemove、scroll)→
InputContinuousLane startTransition包裹的更新 →TransitionLane(可被更高优先级打断)setTimeout、网络请求回调 →DefaultLane
startTransition 的本质
startTransition(() => setState(newValue)) 的核心作用是将包裹的状态更新从 SyncLane 降级到 TransitionLane。降级后的更新:
- 可被 SyncLane 的更新打断(用户输入不被阻塞)
- 时间切片下每个 5ms chunk 结束时可让出主线程
- 如果渲染结果被更新的高优先级更新覆盖,可以直接丢弃未完成的 workInProgress 树
常见失效场景:如果 transition 内的组件树没有被 <Suspense> 包裹,降级后的更新在 commit 阶段仍然不可中断——commit 阶段的 DOM 操作是同步的,时间切片只作用于 render 阶段。
与其他框架调度模型的对比
| 框架 | 调度模型 | 优先级支持 |
|---|---|---|
| React 18+ | Lanes + Fiber 时间切片 | 多级优先级,可中断 |
| Vue 3 | 微任务队列 + 组件深度排序 | 无优先级,FIFO 去重 |
| Solid | 同步 batch | 无优先级,无调度 |
React 需要如此复杂的调度模型,根源在于其响应式粒度——没有细粒度追踪意味着单次更新工作量可能很大,必须支持优先级调度和可中断渲染来保证交互响应性。Solid/Svelte 的细粒度更新天然工作量小,同步执行即可。
引用本术语的文章