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 形态),都只是在这张地图上添加新的分支,而不是重新迷路。

相关推荐
董世昌412 小时前
HTTP协议中,GET和POST有什么区别?分别适用什么场景?
java·开发语言·前端
_OP_CHEN2 小时前
【前端开发之HTML】(二)HTML 常见标签(上):从入门到实战,搞定网页基础排版!
前端·css·html·前端开发·网页开发·html标签
wanzhong23332 小时前
开发日记10-基于vite搭建前端框架
前端框架
满栀5852 小时前
插件轮播图制作
开发语言·前端·javascript·jquery
切糕师学AI2 小时前
Vue 中的计算属性(computed)
前端·javascript·vue.js
程琬清君2 小时前
Vue3DraggableResizable可移动范围有问题
前端·javascript·vue.js
lkbhua莱克瓦242 小时前
CSS盒子模型:网页布局的基石与艺术
前端·css·笔记·javaweb
Curvatureflight2 小时前
前端性能优化指南:从加载到交互的每一毫秒
前端·性能优化·交互
♩♬♪.2 小时前
HTML学校官网静态页面
前端·css·html