Skip to main content

第 24 章:bailout 与优化——memo、自动 memo 与 React Compiler

React 性能优化的本质,一句话:让更多子树跳过重算。跳过这件事叫 bailout,第 6 章介绍过。本章把 bailout 的三个条件、两层时机讲透,再回答"为什么 React Compiler 能自动做这些"。

先记住这一句:bailout 需要三个条件同时满足:本组件 props 没变自身没有更新子树没有活(childLanes)。memo 是"手动告诉 React 满足条件一",React Compiler 是"构建期自动帮你加"。

一图流:要不要重算的决策树

bailout 的三条件

第 6 章讲过 beginWork 开头的检查,它对应三个条件(ReactFiberBeginWork.js:4113):

  1. props 没变oldProps === newProps
  2. 自身没更新checkScheduledUpdateOrContext 返回 false(没有 setState、context 没变)。
  3. 子树没活childLanes 里没有当前渲染相关的 lane(第 6 章那个"预约登记")。

三个都满足,attemptEarlyBailoutIfNoScheduledUpdate3845)直接返回,最后落到 3714,整棵子树跳过。任何一个不满足,就得进 updateXxx 重算

注意"子树没活"是那个反向条件:props 没变但子组件自己 setState 了,父组件照样不重算,但会钻下去让子组件算。所以 bailout 是"本组件省了",不是"整棵都省了"。

memo:手动满足条件一

memo 在第 10 章讲过:它给组件加一层 shallowEqual 比较,props 相等就直接 bailoutOnAlreadyFinishedWork472)。它干的事就是把"props 没变"这个本来靠 React 猜的条件,变成显式保证

为什么 memo 有用?因为父组件重渲染时,它会用新的 props 对象调子组件,oldProps !== newProps 恒为 true,bailout 条件一永远不满足。memo 用浅比较帮 React"看穿":值没变,引用变了没关系,照样跳过。所以 memo 只对"props 频繁变引用但值不变"的场景有收益,无脑包 memo 反而白白多一次浅比较。

自动 memo:React Compiler

React Compiler 是构建期工具(Babel 插件),它把"手动包 memo"这件事自动化:编译时分析每个组件读什么、写什么,自动生成 useMemouseCallbackReact.memo 的等价代码,只有"真的可能被影响"的值才重算。

它跟运行时关系不大,本质是把你会在第 10、15 章手写的优化,提前到编译期做。好处是开发时不用再纠结 memo 该不该包,坏处是编译器必须足够保守,分析不出依赖就多缓存。这也是为什么"依赖不纯、乱改 props"的代码会让 Compiler 失效。

实践顺序

  1. 先靠默认 bailout:别乱包 memo。大部分优化靠"父组件 props 传递稳定"就能吃到默认 bailout。
  2. 定位再手动:用 DevTools 的 Profiler 找到"总是重算"的热点,再针对性地 memo / 稳定引用。
  3. 最后一招 Compiler:代码规范了再上 React Compiler,让它自动 memo。

动手实验

  1. 看三条件:在 beginWork 的 bailout 检查(4113)打断点,更新父组件、子组件 props 引用不变,观察三个条件怎么逐条满足。
  2. 验证 memo 收益边界:给一个 props 永远是字面量的组件包 memo,在 Profiler 里对比,往往没收益(引用每次都一样,React 默认就 bailout 了)。
  3. 看"钻下去":父 bailout 但子 setState,打断点 bailoutOnAlreadyFinishedWork,观察它返回 workInProgress.child(钻下去)而不是 null(整棵跳过)。

小结

  • 优化本质 = 让更多子树 bailout
  • bailout 三条件:props 没变 + 自身无更新 + 子树无活,缺一不可。
  • bailout 分两层:完全跳过(return null)和"本组件省、往下钻"(return child)。
  • memo 是手动保证条件一,只在 props 变引用不变时有收益。
  • React Compiler 是构建期自动 memo,把第 10、15 章的优化提前到编译期。
  • 下一步:如果渲染里抛异常了怎么办,第 25 章错误处理。