16|总复习:把前 15 章串成一张 React 源码主线地图
本栏目是「React 源码剖析」系列:我会以源码为证据、以架构为线索,讲清 React 从构建、运行时到生态边界的关键设计。开源仓库:https://github.com/facebook/react
引言:为什么需要第 16 篇"总复习"?
前 15 篇文章的阅读体验很像"沿着一条高速公路不断加匝道":
- 有的章节在讲 入口与分层(ReactDOM/Renderer 与 Reconciler 的边界)
- 有的章节在讲 调度语言(Lane、纠缠、过期、预热)
- 有的章节在讲 引擎循环(Root Scheduler + WorkLoop,render/commit 分层)
- 有的章节在讲 落地与现实(DOM Host 层、事件系统、Hydration)
- 还有的章节把视野推到 服务端与协议(Fizz、RSC/Flight)
当你分篇阅读时,"局部理解"并不难;难的是把它们组织成一个可复用的心智模型:
- 你看到任何一个函数、一个 flag、一个 lane,都能回答:
- 它在全链路里负责哪一段?
- 它解决了哪个约束冲突?
- 它与哪些模块存在契约边界?
这篇文章就是为了做这件事:把 01-15 的要点压缩成一张"主线地图",再逐章回顾,把你脑子里的知识碎片重新拼成一条可跑通的路径。
0) 先给你一张"主线地图":React 的一条更新如何跑完整套引擎
把前 15 篇压缩成一句话:
React 把更新变成可调度的工作(lane + queue),用 Root Scheduler 在 microtask 里做决策,用 WorkLoop 在 render 阶段做可中断计算,在 commit 阶段做短且确定的副作用事务;DOM/事件/Hydration/SSR/RSC 都是围绕这条主线做扩展与落地。
一张总览图(阅读/复习时可以把它当"导航条"):
Sync
Concurrent
构建产物与入口
createRoot/hydrateRoot
updateContainer / dispatchSetState
scheduleUpdateOnFiber
Root Scheduler: ensureRootIsScheduled
microtask: processRootScheduleInMicrotask
getNextLanes + 纠缠/过期/预热
Sync 还是 Concurrent?
flushSyncWork / performSyncWorkOnRoot
scheduler task / performWorkOnRoot
renderRootSync / renderRootConcurrent
commitRootWhenReady
commitRoot: BeforeMutation/Mutation/Layout/Passive
DOM Host 落地 + 事件系统 + Offscreen/Suspense 特例
Hydration/SSR/RSC 在主线上的组合与扩展
接下来我们按"逐章回顾"的方式,把每一篇放回这张图上。
1) 01|从 Monorepo 到发布产物:你读的源码如何变成你安装的包
这一章的定位是"所有后续阅读的地基":
- 你看到的源文件不等于 npm 包里的代码:release channel、bundle type、fork、wrapper 会改变最终产物。
- 构建系统是架构的一部分 :React 用
scripts/rollup/bundles.js这种 manifest 来定义"要发布什么",而不是把复杂度藏在 rollup 配置里。 - 把团队规范下沉到构建阶段 :例如禁止跨包 import 依赖的
/src/,用构建失败强制边界治理。
你在后面读到 __DEV__、process.env.NODE_ENV、不同入口(react-dom/client 等)时,都要先问:
- 我现在看的逻辑,会落在 stable 还是 experimental?
- 会出现在 ESM 还是 NODE/FB/RN 的哪个 bundle?
2) 02|从 createRoot 到 scheduleUpdateOnFiber:入口层只做"交接"
这一章把"用户调用"变成"引擎更新"的交接点钉死:
react-dom是 Facade(门面) ,真正的引擎在react-reconciler。root.render(children)的核心只有一行:updateContainer(children, root, ...)。- Root 初始化时就会安装事件系统:
listenToAllSupportedEvents(rootContainerElement)。
你应该形成一个稳定认知:
- 入口层不做 diff、不做调度、不做 DOM 操作。
- 入口层只负责:校验、组装、记录边界、把意图交给引擎。
3) 03|从 ensureRootIsScheduled 到 commitRoot:Root Scheduler + WorkLoop 全景
这是"主线地图"里最关键的中枢之一:
scheduleUpdateOnFiber负责"登记"(mark root pending lanes),不负责"施工"。ensureRootIsScheduled把 root 放进链表,并安排一个 microtask :- 事件内多次 setState 先收集
- 事件末尾 microtask 统一决策
- microtask 里:
getNextLanes选下一次要 render 的 lanes- Sync lanes 往往在 microtask 末尾 flush
- 非 Sync 通过 Scheduler 安排时间切片任务
- commit 是分阶段状态机:BeforeMutation → Mutation → Layout → Passive。
这一篇建立的核心心智模型是:
- 调度(何时做)与执行(怎么做)分离。
- microtask 是现代 React 的"节拍器"。
4) 04|Lane 位图到 getNextLanes:优先级不是数字,而是一种"位图语言"
Lane 是 React 并发的"调度语言",这一篇把它讲清:
- Lane 是 位图 ,所以天然支持:
- 合并(
|)、相交(&)、掩码过滤 - "一组 lanes 作为一次渲染的工作集",而不只是一个优先级
- 合并(
- Root 维护多个 lane 集合:
pending/suspended/pinged/warm/expired。 getNextLanes的策略不是"永远最高优先级",而是:- unblocked 优先
- pinged 次之
- 没 pending commit 压力时才做 prewarm
- 避免无意义打断 wip
- **纠缠(Entangle)**是 Transition 语义的实现:同源更新不展示中间态。
这篇读完后,你看任何调度判断(是否 time slice、是否能打断、为什么切 lanes),都能回到 Lane 的集合运算上。
5) 05|Suspense:用 Ping/Retry 把 I/O 完成翻译成"可调度的 work"
Suspense 是"异步现实"与"并发引擎"之间的桥:
use不直接throw thenable,而是throw SuspenseException,再由 WorkLoop 取回 thenable:- 目的:控制流中断但不让 thenable 泄露到用户 try/catch。
- 两种 listener:
- Ping(render 阶段) :root 视角,标记
pingedLanes并重新调度。 - Retry(commit 阶段):boundary 视角,fallback 已成 UI 事实,需要一次更新切回 primary。
- Ping(render 阶段) :root 视角,标记
- Retry 用独立的
RetryLane池,且会做FALLBACK_THROTTLE_MS节流,避免 UI 闪烁与 commit 风暴。
到这一章你应该看到一个贯穿全系列的手法:
- React 经常用"异常"做控制流(render suspend、commit suspend、selective hydration)。
6) 06|为什么 Suspense 会跳过兄弟节点:先快后全的 sibling prerendering
这一章解释一个看似"反直觉"的体验策略:
- 第一次 suspend:常常
skipSiblings=true,尽快让 fallback 出现在屏幕上。 - fallback 可见后:用 Offscreen/RetryLane 驱动 prerender siblings(预热被跳过的内容)。
checkIfRootIsPrerendering用 root lanes 状态推导"当前是否在 prerender 模式"。- prerender 强制走 concurrent work loop:后台热身必须可中断。
这章把 Suspense 从"挂起/唤醒"推进到"挂起后如何继续推进整棵树"。
7) 07|Offscreen:Visibility 不只是 UI 隐藏,更是副作用语义的分层落地
Offscreen 是"可见性"在 Fiber/commit 模型里的工程化表达:
Visibilityflag 横切 Mutation/Layout/Passive。- UI 隐藏:DOM 层
display: none !important(并处理 portal 特例)。 - layout effects:隐藏时 disappear,显示时 reappear。
- passive effects:connect/disconnect/reconnect;隐藏时可能只跑 atomic effects。
你在这一章会得到一个很重要的"副作用分层"直觉:
- UI 是否可见 ≠ 副作用是否应该运行。
8) 08|Commit 阶段:副作用的分层事务 + 静态 flags 的剪枝
这一章把 commit 的"短且确定"落到实现:
flags是单点副作用声明,subtreeFlags是剪枝索引。StaticMask解释了为什么 bailout 子树仍需要冒泡某些信息。- commit 的四阶段与
pendingEffectsStatus状态机。 - 一个容易忽视的分支:commit 也能 suspend(Suspensey commit),为 CSS/图片 decode、view transitions 等与宿主协作服务。
这章的复习重点是:
- React 的可中断性放在 render;确定性放在 commit。
9) 09|DOM Host 层:把抽象语义翻译成浏览器现实
这一章把"抽象动作"落实到 DOM:
- render 阶段 HostComponent update 很粗:
oldProps !== newProps→markUpdate。 - 真正 diff 在 commit:
commitUpdate → updateProperties → setProp。 updateFiberProps是事件系统的关键:事件回调不是靠 add/removeEventListener 更新,而是委托 + 运行时读 props。- 插入/移动与
getHostSibling、moveBefore的工程优化。
读完你应该能清晰区分:
- Reconciler:算"要做什么"。
- Renderer:回答"怎么做"。
10) 10|事件系统:插件化、root/portal 边界、回放与优先级注入
这一章把事件系统放回并发时代的现实:
- root/portal 级"一次性安装监听器"。
- delegated vs non-delegated(不冒泡/特殊事件必须绑在元素上)。
createEventListenerWrapperWithPriority把事件优先级注入更新调度(影响 lane)。- hydration 阻塞:
findInstanceBlockingEvent→ 阻塞、排队、回放。
你应该记住:
- 事件系统不只是"合成事件",它也是"并发调度入口之一"。
11) 11|Hydration 与选择性补水:SSR 续接如何在并发模型下成立
这一章是"SSR 续接协议"的核心:
- root 的关键开关是
isDehydrated。 - hydration 是"claim DOM + 校验 + mismatch 回退"的状态机。
- 选择性补水 用
SelectiveHydrationException中断 work loop,再切换 hydration lane 重启。 - 事件回放不是内部直接调 handler,而是 clone 原生事件再 dispatch,让浏览器分发与 React 插件系统重跑。
它把并发模型的核心约束重新强调了一遍:
- render 必须可中断、可重启;hydration 必须服从这个约束。
12) 12|Fizz:流式 SSR 不是"边 render 边 write",而是 work/flush 解耦的队列系统
Fizz 把 SSR 变成"可回压的调度系统":
Task推进渲染(work),Segment/Boundary组织写出(flush)。- backpressure 用
request.destination = null/drain形成闭环。 - flush 队列顺序体现 SSR 的 UX 策略:先 root shell,再完整边界,再碎片。
- 资源收集(preload/preconnect)也通过 enqueue/flush 接入同一管线。
这章的关键复习点:
- React 在服务端也重复使用"调度 + 队列 + backpressure"这种工程模型。
13) 13|RSC(Flight):不是 HTML,而是可流式传输的"组件模型协议"
这一章把 RSC 的边界钉住:
- Fizz(HTML SSR)与 Flight(RSC 协议)是两套引擎,但共享"流式 + 回压"工程模型。
react-server/react-client是协议运行时;react-server-dom-webpack是绑定层(bundler manifest + DOM/stream 适配)。- ClientReference / ServerReference 把"执行边界"固化到运行时:server 端只能传递引用,不能 dot into client module。
复习时建议记住一句:
- RSC 更像协议与运行时,而不是 ReactDOMServer 的另一个 API。
14) 14|Hook:API 薄、Dispatcher 厚;queue + lane 是连接点
这一章把最常用 API 接回引擎主线:
useState只是dispatcher.useState。- dispatcher 在
renderWithHooks中安装,并在 render 结束立即卸载。 - Hook 状态是链表,挂在
fiber.memoizedState。 dispatchSetState:requestUpdateLane(会读 Transition 上下文)- enqueue update
- 找到 root(沿 return path)
scheduleUpdateOnFiber
复习时你可以用它验证"主线地图"的完整性:
- Hook 更新不是特例,它只是进入同一条
scheduleUpdateOnFiber高速公路的另一个匝道。
15) 15|全链路复盘:把匝道、主路、闸门、落地全部连成闭环
最后一章是对前面所有关键点的再串联:
- 入口:
root.render/Hook/class update 只是 enqueue。 - 总开关:
ensureRootIsScheduled(microtask 批处理)。 - 决策:
getNextLanes(含纠缠/过期/预热/中断策略)。 - 执行:
performWorkOnRoot(sync vs concurrent、yield 策略)。 - 闸门:
commitRootWhenReady(commit 可能等待 host 资源/过渡)。 - 落地:
commitRoot分阶段把副作用落到 DOM/事件/Offscreen/Suspense。
它给你留下一个可复用方法论:
- 不要只问"这个函数干什么",要问"它在全链路里解决哪个矛盾"。
复习清单:你现在应该能用 5 个问题定位任何一段 React 源码
当你再回到 React 源码里迷路时,可以用这 5 个问题做自检:
- 这段逻辑属于哪个层?
- 构建(bundles/wrappers)
- ReactDOM/Bindings(平台门面与细节)
- Reconciler(平台无关内核)
- Scheduler/RootScheduler(CPU 时间分配)
- Server(Fizz/Flight)
- 它处理的是"数据面"还是"控制面"?
- 数据面:pendingLanes、updateQueue、flags、fiber tree
- 控制面:ensureRootIsScheduled、microtask、scheduleCallback、commit 闸门
- 它发生在 render 还是 commit?
- render:可中断,可重启,允许用异常做控制流
- commit:短且确定,不可中断,分阶段落地副作用
- 它与宿主/平台的契约是什么?
- HostConfig 的函数是谁实现?
- DOM 事件/属性/插入删除的现实限制是什么?
- SSR 的 backpressure/stream API 是什么?
- 它在优化什么 trade-off?
- 响应性 vs 吞吐量(time slicing)
- 一致性 vs 性能(commit 分层、节流)
- 可复现 vs 简洁(构建系统)
- 语义边界 vs 易用性(RSC 引用限制)
下一步建议:怎么继续读源码与做练习
如果你想继续深入(而不是停在"概念理解"),我建议你按"可验证的路径"练习:
-
练习 1:从事件到 lane
- 选一个离散事件(click)与一个连续事件(mousemove),对照
ReactDOMEventListener看优先级如何设置,再追到requestUpdateLane。
- 选一个离散事件(click)与一个连续事件(mousemove),对照
-
练习 2:从一次 Suspense 挂起到 retry
- 对照第 05/06/07:
- render 阶段 attach ping
- commit 阶段 attach retry
- fallback 出现后的 prerender siblings
- 对照第 05/06/07:
-
练习 3:从 hydration 阻塞到事件回放
- 从
findInstanceBlockingEvent开始追:- queue continuous
- attempt sync hydration
- retryIfBlockedOn → replay
- 从
-
练习 4:从 Fizz 的 backpressure 到 drain 恢复
- 追
request.destination何时被置空、何时恢复。
- 追
结语:你已经有"主线地图",接下来读任何特性都不会再丢主线
前 15 篇的最终产物不是"记住很多函数名",而是获得一张地图:
- 入口只做交接
- lane 是调度语言
- root scheduler 用 microtask 做批处理决策
- work loop 把可中断计算与确定性副作用分离
- renderer/host 负责把抽象翻译成现实
- SSR/RSC 是把"同样的调度与队列模型"搬到服务端/协议层
当你能在脑子里跑通这条链路时,你再去读任何新特性(更复杂的过渡、更多 host 能力、更多 server 形态),都只是在这张地图上添加新的分支,而不是重新迷路。