跳到主要内容

前言:如何读这本书,如何调试 React 源码

这本书要做的,是用一张图讲清 React 的内部机制,再带着你到源码里验证这张图。

它面向"会写 React、但没看过源码"的开发者。你不需要是内核专家,只需要:会写几行 JSX、装过 Node.js、愿意打开一个编辑器。

这本书在讲什么​

React 的源码读起来难,难在没有地图。十几个包、几万行代码、递归里套递归,你不知道自己站在哪。

所以全书先给你一张地图:

这就是 React 的全部故事:一个工作循环(render + commit),一棵 Fiber 树,一组优先级。

  • render 阶段:算出"界面应该长什么样"(新的 Fiber 树),可中断。
  • commit 阶段:把"样子"变成现实(操作 DOM、执行 effect),不可中断。
  • Scheduler + Lane:决定"什么时候算、先算谁"。

全书第一~第五部分就围绕这张图展开;第六、七部分讲 19 的并发特性与 RSC 怎么在这张图之上生长。

三种读者路径​

你的情况建议路径
只是想搞懂"React 到底怎么工作的"读第 1~6 章(Fiber 与工作循环),跳过细节章节
平时被 Hooks 的坑烦透了直接跳到第三部分(Hooks),随时回翻第二部分的 diff 章节
想成为"能改源码的人"全部按顺序读,每章末尾的「动手实验」别跳过

源码在哪里:固定版本​

本书分析的源码来自 React 官方仓库(facebook/react),全文锁定 v19.2.x(以 v19.2.7 为基准)。所有章节里的 <Src> 链接都指向这个版本。

两个读源码的入口:

  1. monorepo 源码(本书主线):克隆仓库,切到对应 tag。

    git clone https://github.com/facebook/react.git
    cd react
    git checkout v19.2.7

    关键代码都在 packages/ 下。下面这份包地图是本书反复引用的坐标:

    包干什么后面重点看哪章
    react对外 API、Hooks 的入口第三部分
    react-reconciler核心:Fiber、工作循环、diff第一、二、五部分
    react-dom浏览器渲染器、FiberRoot第一、四部分
    react-dom-bindingsDOM 操作、合成事件第四部分
    scheduler任务调度、时间切片第 6、24 章
    react-server / react-clientRSC 的服务端/客户端运行时第 31 章
  2. development bundle(快速验证):任何 React 19 项目里都有现成的开发版源码,node_modules/react/cjs/react.development.js,适合随手查证某个 API 的行为。缺点是没有类型和源码注释,只适合"瞄一眼"。

调试源码的三种手段​

三种手段各司其职,建议先 A 后 C,卡住时再用 B。完整的调试方法论见第 36 章(断点地图、条件断点、火焰图定位热点)。

手段 A:直接读 monorepo 源码(推荐主线)​

不需要构建,clone 下来直接用编辑器打开就能读。

cd ~
git clone https://github.com/facebook/react.git
cd react
git checkout v19.2.7

然后用 VS Code(或任意编辑器)打开 ~/react/ 这个目录。packages/react-reconciler/src/ 下的文件名就是地图——工作循环在 react-reconciler/src/ReactFiberWorkLoop.js,beginWork 入口在 react-reconciler/src/ReactFiberBeginWork.js,completeWork 在 react-reconciler/src/ReactFiberCompleteWork.js。本书所有章节都按这个目录组织,源码位置统一用 <Src> 标注(点开即跳转 React v19.2.7 对应文件与行)。

怎么读最有效率?两个技巧:

  • 跟着 <Src> 链接走:每章讲到某个函数时,正文里会有可点击的 <Src> 引用,点开直接定位到源码对应行。不要从头到尾通读文件——跟着书的线索跳着看。
  • 用 IDE 的"跳转到定义":善用 Ctrl/Cmd + 点击 跟踪函数调用链。比如在 react-reconciler/src/ReactFiberBeginWork.js 里看到 beginWork 调了 updateFunctionComponent,点进去就是函数组件的渲染逻辑。VS Code 里还可以用 Ctrl/Cmd + Shift + F 全局搜索函数名,快速定位所有调用点。

第一次打开 monorepo 可能会觉得文件多。别慌——你只需要看 packages/ 下面这六个目录(其余几十个包是工具链和测试,可以不看):

目录干什么
packages/react/对外 API(createElement、useState 等入口)
packages/react-reconciler/核心:Fiber、工作循环、diff
packages/react-dom/浏览器渲染器入口(createRoot)
packages/react-dom-bindings/DOM 操作、合成事件
packages/scheduler/任务调度、时间切片
packages/shared/全仓库共用的常量和工具函数

手段 B:在 demo 里打断点​

当你书读到一半、脑子里有个"这里到底传了什么参数?走了哪个分支?"时,断点是最直接的答案。整个过程只需要做一次——搭好后整本书的实验都能用它验证。

整个过程涉及两个目录,建议开两个终端窗口:

终端目录干什么
终端 1~/react/构建 React 源码
终端 2~/react-demo/你的测试项目,使用构建产物

下面每一步都标明了在哪个终端执行。

步骤 1:安装依赖(终端 1)

如果你没有 yarn,先装(Node 16+ 自带 corepack,一行搞定):

# 终端 1,任意目录
corepack enable

然后安装 React monorepo 的依赖:

# 终端 1,~/react/ 下
cd ~/react
yarn install

yarn install 会下载大量依赖,大约 3-5 分钟。如果卡在某个包,试试换成国内源:yarn config set registry https://registry.npmmirror.com 后再跑一遍。

步骤 2:构建 dev 版 React(终端 1)

仍在 ~/react/ 下:

# 终端 1,~/react/ 下
yarn build react/index react-dom/index scheduler/index --type=NODE_DEV

这一行做了三件事:

  • react/index react-dom/index scheduler/index——只构建这三个包(不需要构建整个 monorepo)
  • --type=NODE_DEV——构建 dev 版(保留原始变量名和注释,不压缩,能打断点)
  • 产物放在 build/node_modules/ 下,目录结构跟你熟悉的 npm 包一模一样

首次构建大约 2-3 分钟。你会看到终端刷出一排排 "successfully compiled" 信息。完成后用 ls build/node_modules/ 验证——应该能看到 react/、react-dom/、scheduler/ 三个目录。

以后如果你只是想改某个包的代码试试效果,可以只构建那一个,比如 yarn build react/index --type=NODE_DEV,十几秒就完成。

步骤 3:创建一个最小 demo(终端 2)

# 终端 2,~/ 下
cd ~
npx create-react-app react-demo
cd react-demo
npm start

浏览器打开 http://localhost:3000,看到旋转的 React logo 就说明项目跑起来了。确认后按 Ctrl+C 停掉。

用 Vite 也行:npm create vite@latest react-demo -- --template react。Vite 默认端口是 http://localhost:5173。后面的 npm link 步骤完全一样。

步骤 4:用 npm link 把构建产物"偷梁换柱"

这是最关键的一步。npm link 做的事:把终端 1 构建出来的 React 包注册到你本机的全局 npm 缓存,然后让终端 2 的 demo 项目指向它,而不是指向从 npm 下载的版本。

第一步——在终端 1,把三个构建产物注册为"可被 link 的包":

# 终端 1,~/react/ 下
cd ~/react/build/node_modules/react
npm link

cd ../react-dom
npm link

cd ../scheduler
npm link

每条 npm link 成功后都会打印一行路径,类似:

/usr/local/lib/node_modules/react -> /home/你的用户名/react/build/node_modules/react

这行输出的意思是:全局 npm 仓库里多了一个叫 react 的东西,指向你刚构建的目录。

第二步——在终端 2,让 demo 项目用这些 link 过的包:

# 终端 2,~/react-demo/ 下
cd ~/react-demo
npm link react react-dom scheduler

npm link react react-dom scheduler 这一行没有输出(正常),它做的事是把 node_modules/react、node_modules/react-dom、node_modules/scheduler 替换成指向你构建产物的符号链接。

验证是否生效:

# 终端 2,~/react-demo/ 下
ls -l node_modules/react

预期输出——箭头右边必须指向你的构建目录:

lrwxrwxrwx 1 user user 60 ... node_modules/react -> /home/你的用户名/react/build/node_modules/react

如果箭头指向别的地方(比如 ../../.pnpm/...),说明 link 没生效,重新跑一遍 npm link react。

常见问题:Invalid hook call

如果 npm start 后浏览器报 "Invalid hook call",几乎都是因为 node_modules 里存在两份 react:一份是 link 过来的,另一份是某个依赖(如 @testing-library/react)间接引入的。

解决:在终端 2(~/react-demo/)跑:

rm -rf node_modules
npm install
npm link react react-dom scheduler
npm start

原因:npm link 顺序很重要——必须先 npm install 把基础依赖装上,再 npm link 覆盖掉 react 那几个包。

步骤 5:在浏览器里打断点

npm start 启动 demo,Chrome 里打开 http://localhost:3000,按 F12 → 顶部点 Sources 面板。

左侧文件树:展开 react-reconciler/src/(它在 webpack:// 或 vite/modules/ 下面,不同打包工具位置略有不同,但目录名 react-reconciler 不会变)→ 找到 ReactFiberBeginWork.js → 打开后搜索(Ctrl+F)function beginWork → 在这一行的行号上点一下,出现蓝色箭头。

步骤 6:触发,然后读调用栈

在页面上操作(点按钮、输入文字)。断点命中后,浏览器会停在 beginWork 的第一行。这时看 Sources 面板右侧的 Call Stack——从栈底往上读,你能看到:

beginWork ← 你断在这里
performUnitOfWork
workLoopSync
renderRootSync
...
onClick (或者别的用户事件)

这就是一次更新的完整调用链——从事件触发一路跑到 Fiber 处理。第 22 章会把这根链子拆碎了给你看,现在先感受一下它的存在。

本书不要求你每次阅读都构建源码,大部分章节"只读源码 + 跑跑 demo"就够。手段 B 是当你卡在一个具体行为时用的"手术刀"。搭好这套环境后,条件断点、跳过 DEV 噪音、在特定组件上断点等进阶技巧见第 36 章。

另外,如果你用的是 Vite 项目,还有个更快的方法——不用 npm link,直接在 vite.config.ts 里加 resolve.alias:

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
plugins: [react()],
resolve: {
alias: {
react: '/home/你的用户名/react/build/node_modules/react',
'react-dom': '/home/你的用户名/react/build/node_modules/react-dom',
scheduler: '/home/你的用户名/react/build/node_modules/scheduler',
},
},
})

改完后重启 npm run dev 即可。Vite alias 方式没有"多份 react"的隐患,省心。

手段 C:React DevTools​

从 Chrome 应用商店或 Firefox Add-ons 安装扩展。装好后打开任意 React 页面,浏览器右上角会点亮 React 图标(灰色 = 当前页不是 React 项目,蓝橙色 = 是 React 项目且 DevTools 已连接)。

两个核心面板,按下 F12 → 顶部找到 Components 和 Profiler 两个新标签页:

  • Components:拼图图标。选中页面上的一个 DOM 节点,右侧面板显示它对应 fiber 的 props、state(展开 hooks 那一行就是第 13 章的 Hooks 链表)、render 来源("rendered by XXX")。读完第 3 章"Fiber 数据结构"后回来这里,对着面板看真的 fiber,一目了然。这是把书里的图谱搬到眼前最快的方式。

  • Profiler:圆饼图图标。点顶部录制按钮(灰色圆点变成红色),操作页面(点按钮、切换 tab),再点一下停止。你会看到一张火焰图——横轴是时间,竖条越高 = 渲染越久。鼠标悬停在柱子上显示渲染时长和原因,这是第 25 章(bailout)和第 27 章(并发渲染)最直观的验证工具:火焰图里越粗的柱子,越值得翻回源码查为什么没跳过。

试试这个:打开 Components 面板,在你用 create-react-app 建的 demo 里随便选一个 <p> 标签,看右侧 panels——你已经看到 React 内部数据了。读完第 3 章后回来,你会认出 memoizedProps 和 tag。

阅读约定​

  • <Src> 引用:形如 react-reconciler/src/ReactFiberWorkLoop.js:1700 的可点击链接,指向 React v19.2.7 源码的对应文件与行。正文里会频繁出现,点开即可对照。
  • 代码块:带 // → 注释的是"这行是关键",建议跟读;其余可跳读。
  • 图例:所有图都是 Mermaid 手绘风(马卡龙配色)。语义靠形状区分:[] 矩形 = 节点/状态,{} 菱形 = 判断分支,() 圆角 = 入口/出口,(( )) 圆 = 事件/触发;淡紫连线 = 更新主路径。
  • 选讲章节:正文标注"选讲"的章节,与主线索弱相关,赶时间可跳过。

动手:跑一个最小 demo​

整本书讲的工作循环,都可以回到这个最小例子来验证。点下面的按钮试试,这就是 useState 的现场:

实时编辑器
function LiveCounter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount((c) => c + 1)}>点了 {count} 次</button>;
}
结果
Loading...

你每点一次按钮,React 就走完一遍「render → commit」的工作循环——这正是第一部分要图解的东西。

准备好了就翻开第一部分吧。我们从一个 createRoot() 开始。