16|总复习:把前 15 章串成一张 React 源码主线地图

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-domFacade(门面) ,真正的引擎在 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。
  • 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 模型里的工程化表达:

  • Visibility flag 横切 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 !== newPropsmarkUpdate
  • 真正 diff 在 commit:commitUpdate → updateProperties → setProp
  • updateFiberProps 是事件系统的关键:事件回调不是靠 add/removeEventListener 更新,而是委托 + 运行时读 props。
  • 插入/移动与 getHostSiblingmoveBefore 的工程优化。

读完你应该能清晰区分:

  • 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 个问题做自检:

  1. 这段逻辑属于哪个层?
  • 构建(bundles/wrappers)
  • ReactDOM/Bindings(平台门面与细节)
  • Reconciler(平台无关内核)
  • Scheduler/RootScheduler(CPU 时间分配)
  • Server(Fizz/Flight)
  1. 它处理的是"数据面"还是"控制面"?
  • 数据面:pendingLanes、updateQueue、flags、fiber tree
  • 控制面:ensureRootIsScheduled、microtask、scheduleCallback、commit 闸门
  1. 它发生在 render 还是 commit?
  • render:可中断,可重启,允许用异常做控制流
  • commit:短且确定,不可中断,分阶段落地副作用
  1. 它与宿主/平台的契约是什么?
  • HostConfig 的函数是谁实现?
  • DOM 事件/属性/插入删除的现实限制是什么?
  • SSR 的 backpressure/stream API 是什么?
  1. 它在优化什么 trade-off?
  • 响应性 vs 吞吐量(time slicing)
  • 一致性 vs 性能(commit 分层、节流)
  • 可复现 vs 简洁(构建系统)
  • 语义边界 vs 易用性(RSC 引用限制)

下一步建议:怎么继续读源码与做练习

如果你想继续深入(而不是停在"概念理解"),我建议你按"可验证的路径"练习:

  • 练习 1:从事件到 lane

    • 选一个离散事件(click)与一个连续事件(mousemove),对照 ReactDOMEventListener 看优先级如何设置,再追到 requestUpdateLane
  • 练习 2:从一次 Suspense 挂起到 retry

    • 对照第 05/06/07:
      • render 阶段 attach ping
      • commit 阶段 attach retry
      • fallback 出现后的 prerender siblings
  • 练习 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 形态),都只是在这张地图上添加新的分支,而不是重新迷路。

相关推荐
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte7 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc