第 35 章:源码调试方法论——断点、Trace、DevTools(选讲)
选讲。前面 34 章的"动手实验"都在让你打断点。本章把这套能力系统化:怎么拿到能调试的 React、在哪打点、怎么用 DevTools 找到瓶颈。这不是讲 React 本身,是讲你怎么验证这本书讲的一切。
先记住这一句:调试 React 源码三件套:拿到 dev 构建(可断点)、在全书高频函数打点(可观察)、用 DevTools 辅助定位(可量化)。三件套合起来,任何"React 为什么这样"的问题都能落地验证。
一图流:调试工作流
第一步:拿到能调试的 React
前言讲过两条路,这里是实操版:
- node_modules 里的 dev 包(最快):
react/cjs/react.development.js这类文件带完整变量名和注释,能打断点。缺点是 reconciler 的核心(react-reconciler)不在 npm 上,看不了 Fiber 内部。 - monorepo 源码构建(完整):clone
facebook/react,git checkout v19.2.7,按贡献指南构建 dev 版。产物在build/node_modules/,替换进 demo 项目,全部 reconciler 源码都能断点。本书所有<Src>行号都对着这份源码。
第二步:在哪些函数打点
全书出现频率最高的几个函数,就是你的"断点地图"(都在对应章节验证过行号):
| 想观察什么 | 断在哪 |
|---|---|
| 一次更新从哪开始 | dispatchSetState(ReactFiberHooks.js:3599) |
| 调度 | scheduleUpdateOnFiber(ReactFiberWorkLoop.js:916) |
| 每个 fiber 的处理 | beginWork(ReactFiberBeginWork.js:4091) |
| 算新 state | updateReducer(ReactFiberHooks.js:1294) |
| 落 DOM | commitMutationEffects(ReactFiberCommitWork.js:1944) |
| 跑副作用 | commitLayoutEffects(ReactFiberCommitWork.js:2876) |
技巧:条件断点。beginWork 会被调用几千次,全打太吵。右键断点设条件,比如 workInProgress.tag === 5(只看 DOM 元素),或 workInProgress.type?.name === 'Counter'(只看某个组件)。这样几千次命中里只停下来你要的那几次。
第三步:观察什么
命中断点后,重点看三样:
- 调用栈:从栈底往上,能看清"这次 beginWork 是哪个调度链过来的"(第 21 章那条 setState 接力线,栈就是它的活地图)。
- 关键变量:
workInProgress.tag(第 3 章的工种)、current和workInProgress的alternate(双缓冲)、lanes(优先级)。 - 跳过 DEV 分支:React 源码大量
__DEV__代码,很多和核心逻辑无关。要看清主流程,可以直接在if (__DEV__) { ... }之外的代码里打点,或者构建时用--type=PROD(生产版省掉 DEV 噪音,但变量名会被压缩,调试信息少)。通常"dev 构建 + 跳过 DEV 块"是最佳组合。
第四步:用 DevTools 量化
断点适合"看某一次",DevTools 适合"看整体":
- Components 面板:选一个组件,右侧就是它的
memoizedProps/memoizedState/ Hooks 链表(第 12 章那个链表直接可视化)。看 fiber 长什么样,这里最直观。 - Profiler 火焰图:每帧谁在渲染、渲染多久。定位"哪个组件是热点",再用断点去看它的
beginWork为什么没 bailout(第 24 章的三条件,对着火焰图找违反者)。
常见坑
- 生产包没调试信息:
react的production.js是压缩+混淆的,别在那上面断点。 - 多份 React 共存:项目里装了两份 react(版本/实例不同),DevTools 会报"doesn't match",也会让
<Src>行号对不上。用npm ls react排查。 - 把 dev 构建当生产:dev 版有 StrictMode 双调用、大量警告,性能数字不能当基准。基准用生产,调试用 dev。
动手实验
- 搭调试环境:按前言 clone react@v19.2.7 构建 dev 版,替换进一个最小 demo,确认能对
beginWork断点。 - 条件断点:在
beginWork设条件workInProgress.type?.name === 'Counter',点按钮,确认只对 Counter 停下。 - 火焰图定位热点:Profiler 记录一次慢渲染,找最粗的柱,回去用第 24 章的三条件检查它为什么没 bailout。
小结
- 三件套:dev 构建 + 高频函数断点 + DevTools 量化。
- 断点地图:
dispatchSetState/scheduleUpdateOnFiber/beginWork/updateReducer/commitMutationEffects。 - 条件断点把几千次命中筛成几次,只看你关心的。
- 调用栈是第 21 章那条接力线的活地图。
- DevTools 看整体(火焰图定位热点),断点看局部(为什么没 bailout)。
- 下一步(选讲):DevTools 自己是怎么实现的,第 36 章。