# react18并发调度源码主线
# 这篇解决什么问题
很多人知道 React 18 有并发渲染,但一读源码就迷路:
lane、优先级、可中断渲染、commit 到底怎么串起来?
这篇给你一条“能落地的阅读主线”,重点回答三件事:
- 更新是怎么被打上优先级的
- 为什么渲染可以被中断与恢复
- commit 为什么必须同步且不可中断
# 先建立总流程(从一次 setState 开始)
一次更新大致经过:
- 触发更新(
setState/ dispatch) - 创建 update,并分配 lane(优先级位)
- 向 root 标记待处理 lane
- 调度器决定何时执行 render(可能并发)
- render 阶段构建 workInProgress 树(可中断)
- render 完成后进入 commit(同步、不可中断)
- 提交 DOM 变更 + 执行副作用(layout/passive)
你可以把它理解为:
lane 决定“先做谁”,scheduler 决定“何时做”,fiber 决定“如何做”。
# 一、Lane 模型:React 18 的优先级核心
# 1)为什么不用一个数字优先级,而用 lane 位掩码
lane 本质是 bitmask(位集合),不是单一值。
这样设计可以同时表达:
- 多个不同优先级更新并存
- 某些 lane 合并处理
- 某些 lane 饥饿时被提升(过期)
这比“单任务队列 + 数字优先级”更适合 UI 更新场景。
# 2)常见 lane 语义(理解层)
不强求记常量名,先记语义层:
- Sync 类:必须尽快完成(离散输入)
- Default 类:常规更新
- Transition 类:可延迟、可打断
- Idle 类:空闲时再做
# 3)一次更新如何拿到 lane
requestUpdateLane(概念入口)会综合:
- 当前事件优先级(点击、输入、滚动等)
- 当前执行上下文(是否在 transition 内)
- 现有 pending lane 状态
然后给 update 打上 lane,并向上冒泡到 root。
# 4)root 上的 lane 状态
root 会维护多组 lane 集合(可理解为状态桶):
- pending:待处理
- suspended:被挂起(如 Suspense)
- pinged:挂起资源恢复后可重试
- expired:超时需尽快处理
这套状态机是“可恢复并发”的关键基础。
# 二、优先级桥接:事件 -> lane -> Scheduler
React 内部有两套优先级语义:
- React 语义优先级(lane)
- Scheduler 任务优先级(Immediate/UserBlocking/Normal...)
调度时会把 lane 映射到 Scheduler 优先级,再把任务丢给调度器。
# 关键价值
- React 保留了自身更新语义(lane)
- 复用 Scheduler 做时间片与任务让渡
这就是“渲染语义”和“执行时机”解耦。
# 三、可中断渲染:为什么 render 可以停
# 1)render 阶段在做什么
render 主要是“算树”:
- 从当前树(current)克隆/复用为 workInProgress
- 执行
beginWork(算子节点) - 执行
completeWork(冒泡、收集 effect)
这阶段不直接改真实 DOM,所以可以中断。
# 2)work loop 与时间片
并发模式下,work loop 会周期性检查是否该让出主线程(shouldYield)。
如果时间片到、且有更高优任务进来,就先暂停当前 render。
# 3)恢复机制
因为 workInProgress 树保存了进度,下次可以从中断点继续。
如果有更高优 lane 更新,可能会重开一次 render(丢弃部分旧进度)。
# 4)为什么说“并发不是并行”
JS 仍是单线程。
并发在这里是“任务可切片、可抢占、可恢复”,不是多核并行执行。
# 四、Transition:可延迟更新的产品化接口
startTransition / useTransition 的本质:
把更新打到 transition lane,让它在高优交互后再推进。
典型场景:
- 输入框内容立即更新(高优)
- 大列表筛选结果延迟更新(transition)
这就是 React 18 “既快又稳”的核心手法:高优交互不被重渲染拖慢。
# 五、commit 阶段:为什么必须同步不可中断
render 可中断,但 commit 必须一次完成。原因很简单:
commit 会触达真实世界(DOM/Ref/生命周期副作用),中断会造成 UI 不一致。
# commit 三个子阶段(理解层)
- before mutation:提交前准备(如快照)
- mutation:真正改 DOM
- layout:执行 layout effect / ref 等同步副作用
之后异步调度 passive effects(useEffect)。
# 一句话记忆
- render:可打断,算结果
- commit:不可打断,落结果
# 六、Suspense 与并发调度如何配合
当 render 中遇到“数据未就绪”:
- 对应 lane 可能进入 suspended
- 展示 fallback
- 数据 ready 后触发 ping,lane 进入可重试
这就是 Suspense 能和并发调度协同的底层机制:
不是“失败重来”,而是“挂起等待后定向恢复”。
# 七、源码阅读顺序建议(实操)
下面给一个“少走弯路”的阅读路径(按职责):
- 更新入口:
setState/dispatch 到 update 创建 - lane 分配:
requestUpdateLane相关链路 - root 调度:
ensureRootIsScheduled - render 主循环:
workLoopConcurrent/performUnitOfWork - Fiber 两阶段:
beginWork/completeWork - commit:
commitRoot主流程 - effect:layout/passive 的执行时机
- Suspense:suspend -> ping -> retry
阅读方法建议:
- 先画时序图,再看源码细节
- 一次只追一个场景(如输入 + 列表 transition)
- 不要第一天就纠结所有 lane 常量值
# 八、常见理解误区
# 误区 1:React 18 默认所有更新都并发
不是。并发是能力,不是所有更新的强制模式。
# 误区 2:有了并发就不会卡
并发只能改善调度与响应,不会消灭重计算本身。
昂贵计算仍需拆分、缓存、虚拟列表等手段。
# 误区 3:useTransition 能替代防抖节流
它解决的是渲染优先级,不是请求风暴治理,两者职责不同。
# 小结(主线回看)
React 18 并发调度可以压缩成一条主线:
update 打 lane -> scheduler 按优先级调度 -> render 可中断计算 -> commit 同步落地。
如果你把这条线吃透,再回头看 Fiber、Suspense、Transition,都会从“概念”变成“可推演的系统”。