React Compiler 1.0浅析:从配置到优化
React Compiler 1.0 也发布大半年了。如果你还没开始用,或者试了又退回来,你可以看看这篇文章。
先说结论:这东西是一个给 React 代码做自动优化的编译时工具。你写代码,它帮你算缓存,不用再手动加 useMemo 和 useCallback。但落地过程有几个坑。
现在是什么状态
React Compiler 1.0 稳定版在 2025 年 10 月发布。到了 2026 年中,框架生态基本全覆盖了:
- React 19+:原生支持
- Next.js 16:开箱即用,零配置
- Next.js 15.3.1+:通过
experimental.reactCompiler开启 - Expo SDK 54+:默认开启,React Native 也能用上
- Vite 8:配置变了,下面细说
Vite 8 的配置要特别注意。 @vitejs/plugin-react v6 把内置 Babel 换成了 oxc(Rust 写的解析器),JSX 转译和 Fast Refresh 全走 Rust,速度确实快了很多。但代价是旧的 react({ babel: { plugins: [...] } }) 写法废了。现在得外挂 @rolldown/plugin-babel:
// vite.config.js - 2026年新写法
import { defineConfig } from 'vite'
import react, { reactCompilerPreset } from '@vitejs/plugin-react'
import { babel } from '@rolldown/plugin-babel'
export default defineConfig({
plugins: [
babel({
include: /\.[jt]sx?$/,
babelConfig: reactCompilerPreset(),
}),
react(),
],
})
注意两件事:babel() 必须在 react() 之前;网上大半教程还在教老写法,直接抄到 Vite 8 项目里 build 会崩。
编译器实际做了什么
React 的重新渲染问题本质上是 JS 每次渲染都创建新的对象和函数引用,导致下游组件觉得"东西变了"其实根本没变。手写 useMemo 和 useCallback 就是专门治这个的。
编译器把这个问题在构建阶段就解决了。它对你的组件代码做静态分析(控制流分析 + 数据流分析 + SSA IR),然后只在实际需要的地方插入缓存,粒度比手写更细。
拿 useMemo 举例,你手写的时候只能对整个对象缓存:
const formatted = useMemo(
() => ({
label: formatLabel(entity),
total: entity.items.reduce(...),
status: entity.state === "active" ? "green" : "red",
}),
[entity]
);
如果只是 entity.state 变了但 items 没变,整块还得全算一遍。编译器能对子表达式级别做缓存,只重算变化的部分。
这是一种你平时感知不到的优化,但在复杂列表渲染或派生数据多的场景下能看出差距。
18个月回顾:实际效果怎样
Sascha Becker 写了一篇很详细的 18 个月回顾,里面有几个数据值得关注:
性能数据: Meta 那边报告首屏加载快 12% 左右,页面间导航有改善,某些交互超过 2.5 倍提速。内存占用持平。
生态现状一句话总结: 新项目基本没问题(greenfield is solved),老项目是个工程(brownfield is a project)。
最大的收益不是性能,是认知负担减轻。 这不只是个口号。Meta 内部跑了一年多的反馈也类似:最大的提升不在 benchmark 数字,而在团队写代码的时候少想了一件事。
以前写组件,脑子里同时要跑两条线:业务逻辑对不对 + 这里要不要加 memo。每写一个计算就得犹豫一下。现在不用了——先写干净的代码,编译器兜底。
那些编译器处理不了的情况
违反 Rules of React
编译器假设你遵守 Rules of React。如果你不遵守,编译器不会报错——它只是静默跳过你的组件。
哪些常见操作会触发跳过?
- render 里改了 props 或闭包变量
- render 里读
ref.current(取 DOM 尺寸、判断元素状态) - 修改入参默认值
function Field({ value = defaultValue }) {
value = value ?? fallback; // 编译器跳过
}
- async 组件函数
这些都是静默行为。你收不到 error,只是在 React DevTools 里看不到优化标记。
解决:装 eslint-plugin-react-compiler,在 CI 里把规则设成 error,没通过不允许合并。
旧版库兼容问题
不少老的状态管理库、表单库、拖拽库其实偷偷违反了 Rules of React,只是以前没编译检查没人发现。打开编译器后,这些库可能导致组件多渲染。
不是编译器搞坏了它们,是它暴露了它们一直存在的问题。
落地方案:分四步走
根据社区半年多的实践,推荐这样落地:
第一步:ESLint
不管用什么框架,先把 eslint-plugin-react-compiler 装上,规则设 error。跑一遍看项目有多少违规,提前摸底。
这个步骤意义很大:你能在跑编译器之前就知道它能覆盖多大比例。花十分钟就行。
第二步:annotation 模式
别一上来就全量开。官方有分级安全策略:
compilationMode: 'annotation':只处理标注了"use memo"的组件compilationMode: 'infer':让编译器自己决定compilationMode: 'all'(默认):全量处理
建议从 annotation 开始,挑 2-3 个关键组件试点,观察 DevTools 里渲染树有没有异常。
第三步:全量打开
确认无误后切到 all。旧的 useMemo 和 useCallback 不用急着删——编译器是增量的,跟你的手写缓存不冲突。先跑稳定再说。
第四步:清理
把那些为了防性能问题而加的 memo 逐步删掉,让代码回到最原始的形态。目标是你写业务逻辑,编译器做优化。
常见疑问:要不要删掉老代码?
这是每个团队都会问的问题。官方建议和社区实践一致:
新代码: 别写了。除非你有具体的、经过测试的理由。
老代码: 暂时留着。不要搞大扫除。删除已有的 useMemo/useCallback 可能微妙改变编译器的输出。如果某个 effect 依赖了引用稳定性,删了反而可能导致 effect 意外触发。
useMemo 和 useCallback 现在应该视为逃生阀(escape hatch),不是默认工具。需要精确控制的时候再用。
一句话总结
React Compiler 1.0 在新项目上是零成本的自动优化,在老项目上是一次需要规划的迁移。最大的收益是写代码的时候少操一份心,不是帧数提升了多少。
如果是新项目,直接用。如果是老项目,先跑 ESLint 看看情况,再决定怎么走。
本文首发于 auraimagai.com。
更多推荐
所有评论(0)