React Compiler 与手动 Memoization 对比
多年来,React 性能优化几乎只有一种方式:在代码库中到处使用 useMemo、useCallback 和 React.memo,并祈祷依赖数组写对了。React Compiler 改变了这一局面。但改变了多少?手动 memoization 在现代 React 应用中是否还有一席之地?
下面是你真正需要了解的内容。
核心要点
- React Compiler 是一款构建时工具,它基于静态分析自动应用等效于
React.memo、useMemo和useCallback的优化。 - 它擅长处理常见模式,如回调 props、children 作为 props,以及自定义 Hook 的返回值。
- 在以下场景中,手动 memoization 仍然重要:返回不稳定对象的第三方 Hook、effect 依赖项,以及经过性能分析确认的瓶颈处。
- 新的思维方式是:默认编写干净的组件,只在有可衡量理由时才进行有意识的 memoization。
React Compiler 的作用
React Compiler 是一款构建时工具,可以自动对组件、其 props 以及 Hook 返回值进行 memoization。它在编译时分析你的代码,并应用等同于用 React.memo、useMemo 和 useCallback 包裹的优化,而你无需手动编写这些代码。
它现在已稳定并可用于生产环境,Meta 已将其用于生产,并支持 Babel、Vite、Metro 和 Rsbuild。Next.js 15.3.1+ 支持通过 SWC 调用 React Compiler 的路径,以提升构建性能。
关键词是 构建时。React Compiler 并不是任意值的运行时缓存。它专注于基于代码结构静态分析的组件重渲染优化。
React Compiler 自动处理 Memoization 的场景
编译器能够干净利落地处理常见情况:
- 简单的状态变化 —— 不依赖于变化状态的兄弟组件不会重新渲染。
- 带回调的 Props —— 作为 props 传入的内联箭头函数会被正确 memoize,即使在嵌套场景中也是如此。
- Children 作为 Props —— 那个臭名昭著的
useMemo(() => <Child />, [])模式不再必要。 - 自定义 Hook —— 返回值会根据其实际依赖项进行 memoize。
第三种情况值得驻足。大多数开发者在手动处理时都会出错,而编译器能自动正确处理。
为何 React 中的手动 Memoization 仍然重要
跨多个代码库的实际测试讲述了一个更接地气的故事。编译器能很好地处理隔离、自包含的组件,但在第三方库返回未经 memoize 的对象时会力不从心。
最典型的例子:React Query 的 useMutation 每次渲染都会返回一个新对象。如果你的 onDelete 回调依赖于 deleteCountryMutation 而不是直接依赖于解构出来的 mutate 函数,编译器就无法将其稳定化。一旦明白原因,修复就很简单:直接解构出 mutate。但编译器没办法替你做出这个判断。
// 编译器友好:稳定引用
const { mutate: deleteCountry } = useMutation(...)
// 编译器不友好:每次渲染都是新对象
const deleteCountryMutation = useMutation(...)
const onDelete = () => deleteCountryMutation.mutate(name)
其他手动控制仍占优的场景:
- Effect 依赖项 —— 当你需要一个 memoized 值,专门用来防止
useEffect反复触发时。 - 动态列表 —— 在
.map()中渲染的行,如果提取为带有稳定keyprops 的命名组件会更有利。编译器在 组件内部 的优化效果优于 跨内联渲染输出 的优化。 - 性能分析后的调优 —— 如果你已测得某个具体瓶颈,显式的
useMemo能提供编译器启发式无法企及的精确控制。
Discover how at OpenReplay.com.
你的 React 编码习惯应当如何转变
思维模型的转变很直接:不再防御性地 memoize,改为有意识地 memoize。
在编译器出现之前,默认做法是为了以防万一而包裹一切。这带来了噪音、维护负担,偶尔还会引入隐蔽的 Bug(例如官方文档现在着重指出的 useCallback 与内联箭头函数陷阱)。
启用 React Compiler 后,新的默认做法是编写干净、简单的组件,让编译器处理优化。只有在有具体、可衡量的理由时,才使用 useMemo 或 useCallback,而不是把它当作条件反射。
现在可以开始养成的两个实用习惯:
- 将列表项提取为命名组件 —— 用
<CountryRow />取代.map()内的内联 JSX。 - 从第三方 Hook 中解构出稳定的值 —— 直接使用
mutate,而不是整个 mutation 对象。
实用结论
React Compiler 是 React 性能优化领域的真正进步。它消除了使代码库杂乱的大部分防御性 memoization,并且在处理棘手的 children-as-props 模式上,比大多数手动操作做得更好。
但它并不能替代对 React 重渲染机制的理解。能从编译器获益最多的开发者,是那些仍然理解 useMemo 与 React Compiler 之间权衡、并知道何时该使用哪一个的人。
结论
React Compiler 标志着我们思考 React 性能方式的一个真正转折点。把每个值和函数都用 memoization 辅助工具包裹起来的条件反射式习惯,已不再是正确的默认做法。写更简洁的代码,让编译器发挥作用,在手动优化前先做性能分析。把手动 memoization 留给编译器看不到的场景,尤其是围绕第三方 Hook 和经测量确认的瓶颈。这正是在 2026 年及未来仍然站得住脚的方法。
常见问题
需要。编译器处理了大部分防御性 memoization,但你仍然需要理解这些 Hook,以应对它无法优化的场景,例如第三方库返回的不稳定对象、effect 依赖项以及经过测量的性能瓶颈。理解重渲染的工作原理还能帮你编写出编译器能更有效优化的代码。
不会。React Compiler 设计上能与现有的 useMemo、useCallback 和 React.memo 调用共存。你可以渐进式地采用它,无需移除当前的 memoization。随着时间推移,你可以清理冗余的手动 memoization,但在项目中启用编译器前,无需急于剥离这些代码。
useMutation 返回的 mutation 对象在每次渲染时都拥有新的引用。编译器无法判断其内部方法是稳定的,因此任何依赖整个对象的回调都会被重新创建。直接从 useMutation 中解构出稳定的 mutate 函数可以解决这个问题,并让编译器正确地 memoize 依赖该函数的回调。
它支持 Babel、Vite、Metro 和 Rsbuild,Next.js 15.3.1 及更高版本通过 SWC 启用它。大多数现代 React 配置只需极少配置即可采用。在将其集成到生产代码前,请查阅官方 React Compiler 文档,以了解最新的受支持工具链列表以及任何框架特定的配置步骤。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.