去年 10 月,React 官方发布了 React Compiler 1.0 稳定版。
现在半年过去了,真放到生产项目里跑了几轮,有些感受跟当初想的完全不一样。今天把真实体验摊开说说,哪些是真香,哪些是坑。
先说说半年后的生态现状
现在 React Compiler 的覆盖情况是这样的:
- React 19 原生支持,新项目默认就用上了
- Next.js 15.3.1+ 稳定支持,Next.js 16 开箱即用
- Expo SDK 54+ 默认已开,React Native 也能享受到
- Vite 8 的集成出现了新变化
这里面有个坑值得单拎出来说。
Vite 8 的配置变天。
@vitejs/plugin-react v6 做了个大动作:把内置的 Babel 换成了 oxc(Rust 实现的解析器)。JSX 转译和 Fast Refresh 全走 Rust,编译速度确实快了很多,但代价是旧的 react({ babel: { plugins: [...] } }) 写法直接废了。
现在要跑 React Compiler,得外挂 @rolldown/plugin-babel:
javascript
// 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(),
],
})
网上一大半教程还在教老写法。你要是抄到 Vite 8 的项目里,build 直接崩。注意 babel() 必须在 react() 之前。
对于还没升 Vite 8 的项目,旧写法继续用就行,不冲突。
Next.js 16 开箱自带。
这是体验最好的:装好就是开的,零配置。而且 Next 15.3.1+ 也可以用 experimental.reactCompiler 打开,门槛极低。
真正有用的地方,和当初说的不一样
最大的收益不是性能,是认知
这是最出乎意料的。
半年前所有人都在吹"编译器帮你省 useMemo",但实际跑下来,性能提升在大部分场景下并不明显。页面该 60fps 的还是 60fps,该卡的地方不是因为没写 memo,而是因为代码本身设计有问题。
编译器真正改变的是 写代码的心态。
以前写一个组件,脑子里要同时想两件事:业务逻辑对不对 + 这地方要不要加 memo。每写一个计算就得犹豫一下。code review 里周期性地出现"这里加个 useCallback 吧"的 comments。
现在不用想了。先写干净的代码,编译器兜底。那种"写代码之前先算依赖"的 mental overhead 消失了。这个体验的提升比帧数提升更实在。
InfoQ 在 2025 年底的报道里提到,Meta 内部跑了 React Compiler 一年多,反馈也是类似的:最大的收益不在 benchmart 数字,而在团队认知负担的减轻。
粒度比你手写精细
Sascha Becker 在 18 个月回顾文章里提到一个细节:你手写 useMemo 的时候,只能对整个计算对象做缓存。编译器能做到的是对子表达式级别做缓存。
举个例子,你手写一般是这样的:
javascript
const formatted = useMemo(
() => ({
label: formatLabel(entity),
total: entity.items.reduce(...),
status: entity.state === "active" ? "green" : "red",
}),
[entity]
);
如果只是 entity.state 变了而 items 没变,整个 formatted 还是得全算一遍。编译器的粒度更细,只会重新算变化的部分。
这种细节层面的优化,大多数场景不会让你有感知,但在复杂列表渲染的场景下,比如一个列表项里有多个派生数据,确实能看到渲染树的缩小。
那些编译器搞不定的地方
老项目别急着打开
Reddit 上一个跑生产 6 个月的经验帖提到的最重要教训是:React Compiler 最难的不是装上去,是装上去之后。
打开编译器,CI 过了,跑起来也没报错,然后你发现某些组件莫名其妙地多渲染了几次。因为编译器对不符合 "Rules of React" 的代码会静默跳过,而不是给你报错。
哪些代码会触发静默跳过?
- render 里改了 props 或闭包变量。很多人嘴上说"不这么干",但拷问细节就露馅了。
- render 里读 ref.current。这是个很常见的操作:取 DOM 尺寸、判断元素状态,编译器直接跳过你的组件。
- 修改入参默认值:
javascript
function Field({ value = defaultValue }) {
value = value ?? fallback; // 编译器跳过
// ...
}
- async 函数。组件里用 async 的也是跳过。
每种跳过都很安静。你不会收到 error,只是在 React DevTools 里看不到那个代表已优化的 ✨ 徽章。
解法:装 eslint-plugin-react-compiler,把 react-compiler/react-compiler 规则设成 error。在 CI 里翻成 error,没通过就不允许合并。这是唯一靠谱的做法。
逃生阀 "use no memo"
如果编译器真的对一个复杂组件判断错了,或者迁移成本太高,可以在函数顶部加一行:
javascript
function VeryComplicatedThing() {
"use no memo";
// 编译器跳过这个函数
}
但这东西最好别变成习惯。有团队吐槽这成了他们的永久技术债,迁移完了懒得改回去。记住这应该是个临时方案。
2026 年正确入门的步骤
根据半年来的踩坑经验,按这个顺序走最稳:
第一步:ESLint 先上
不管用什么框架,先把 eslint-plugin-react-compiler 装上,规则设 error。跑一遍看看你的项目有多少违规,有多少组件符合条件。这一步不费什么时间,但能提前知道编译器能覆盖多少。
第二步:打开编译器,用 annotation 模式
别一上来就 all。官方设计了几档安全策略:
compilationMode: 'annotation':只处理标注了"use memo"的组件compilationMode: 'infer':让编译器自己决定compilationMode: 'all'(默认):全量处理
建议先从 annotation 开始,挑两三个关键组件测试,看有没有异常。
第三步:全量打开,跑一段时间
确认无误后切到 all。旧的 useMemo 和 useCallback 不用急着删,编译器是增量的,你的手写缓存跟它不冲突。跑稳定了再慢慢清理。
第四步:清理老代码
把那些为了防性能问题而加的 memo 删掉,让代码回到最原始的形态。你写业务,编译器优化------这才是最终形态。
一句话总结
React Compiler 1.0 不是神器也不是噱头。它对新项目是白送的优化,对老项目是一次值得做的迁移。最大的价值不是帧数提升了多少,而是你写代码的时候少想了一件事。
至于要不要现在下手,如果项目是新的,直接上。如果是老项目,先跑 ESLint 看看情况再做决定。
更多阅读
-
React Compiler 1.0 来了:从今以后,useMemo 和 useCallback 可以删了? ------ 原文地址
-
Web Components + Astro + htmx: The 2026 Lightweight Frontend Trio ------ 另一篇关于前端框架趋势的文章,从 React 优化的反面给出思考