React Compiler 上线十八个月:轨迹、争议与下一步
- 原文链接:saschb2b.com/blog/react-…
- 原文作者:Sascha Becker
发布日期:2026 年 4 月 22 日 阅读时长:11 分钟
自从 React 19 以稳定版形态交付 Compiler 后,生态已经走过了可预期的几个阶段:框架集成、工具成熟、社区争论。本文回顾过去十八个月,也尝试解读 React 团队已经释放的后续信号。
React 19 在 2024 年底发布时,React Compiler 也一并稳定。十八个月后,生态对这类平台级变化的反应基本符合预期:先公告、再框架集成、然后工具成熟,最后进入更慢、更混乱的阶段——讨论这件事到底意味着什么。
这篇文章是对这条轨迹的回顾,也是在解读 React 团队对下一阶段的公开信号。它不是一篇入门解释文,虽然文中会包含一些解释性内容供参考。我自己没有做过大型生产迁移。下文主要基于公开文档、RFC、React 团队演讲,以及文末列出的早期采用者实践总结。
Compiler 最大的遗产不会是跑分数字,而是它淘汰的一整类 bug。“你漏了一个
useCallback依赖”已经不再是一个讨论话题。
十八个月的轨迹
从 React Conf 2024 宣布稳定版开始,Compiler 的发展基本沿着可预期路径推进:发布、集成、工具化、争论。
发布与早期集成。 React 19 发布后的几个月里,Compiler 在实践层面的主要工作几乎都是配置。Next.js、Expo、TanStack Start,以及一批基于 Vite 的框架,都把它接进了各自的构建流水线。对新项目来说,故事变成了“默认开启,需要时可关闭”。对存量项目来说,一条以 ESLint 插件为“可迁移性前置信号”的路径逐步清晰起来。
安静的中段。 这一阶段比最初热度暗示的要安静。早期采用团队的态度更多是谨慎而非兴奋,他们报告的收益也偏“朴素”:代码评审里暴露的重复渲染问题更少,“为什么这里这么慢”这类 bug 的占比下降,代码库不再继续累积手写 memo 化蔓延。社区原本以为会大量出现的“惊艳跑分文章”并没有真正到来,因为 Compiler 最大影响是避免 bug,而不是制造标题党数字。
生态清算期。 到 2025 年末,讨论焦点从“要不要采用”转向“那些会被它搞崩的库怎么办”。多数团队目前仍卡在这里。Rules of React 原本一直成立,但现在在构建期变得可强制执行;而生态里有相当多代码其实一直在悄悄打擦边球。较老的状态库、遗留表单方案、一些拖拽实现、以及几类常用工具 Hook 都在列。Compiler 并不是“把它们弄坏了”,而是让它们“本来就有问题”这件事变得可见。
用一句话概括当前状态:绿地项目已经解决,棕地项目仍是工程。
Compiler 做了什么
简述如下,供参考。Compiler 是一个构建时转换器。它读取你的组件,假定它们遵守 Rules of React,然后在有帮助的地方插入 memo 化。Compiler 本身不引入运行时成本;输出仍然是普通 React。它的粒度通常优于人工写法:人工 useMemo 往往记忆化整个派生对象,而 Compiler 可以记忆化内部子表达式,因此改动一个字段不会让其他字段全部失效。
实践中,类似下面的代码:
const DashboardRow = memo(({ entity, onSelect }: Props) => {
const formatted = useMemo(
() => ({
label: formatLabel(entity),
total: entity.items.reduce((sum, i) => sum + i.value, 0),
status: entity.state === "active" ? "green" : "red",
}),
[entity],
);
const handleClick = useCallback(() => {
onSelect(entity.id);
}, [entity.id, onSelect]);
return (
<Row onClick={handleClick}>
<Label color={formatted.status}>{formatted.label}</Label>
<Total>{formatted.total}</Total>
</Row>
);
});
在启用 Compiler 的代码库里,会变成像这样:
function DashboardRow({ entity, onSelect }: Props) {
const label = formatLabel(entity);
const total = entity.items.reduce((sum, i) => sum + i.value, 0);
const status = entity.state === "active" ? "green" : "red";
return (
<Row onClick={() => onSelect(entity.id)}>
<Label color={status}>{label}</Label>
<Total>{total}</Total>
</Row>
);
}
memo 包装器、useMemo、useCallback:都消失了。交互中的最直观收益,是重复渲染的扩展形态变化。典型失败案例是:列表场景里,父组件 useCallback 里一个错误依赖,会导致每次按键都让所有行重渲染;成本会随列表大小线性上升,直到出现输入卡顿。Compiler 取消了这种扩展关系。只要某一行的 props 没变,它就不会被触碰,不受列表规模影响。
为完整起见也要说成本:公开基准里,构建时间通常会上升到“几十个百分点”的量级(增量构建大多不受明显影响);由于内联 memo 化辅助逻辑,包体体积也会增加低个位数百分比。这两项都与项目有关;在你自己测量前,不要盲信任何“精确数字”。
这不是银弹。
Compiler 解决的是重复渲染问题。它不解决网络瀑布、过度抓取、包体过大,或初始 JavaScript 过慢。如果应用感到慢,先测量。很多团队在采用 Compiler 后,反而更容易看清真正瓶颈原来在别处。
Compiler 失效的地方
十八个月的生产实践让“Compiler 不会处理什么”这张清单更清晰。下面四类模式,几乎出现在我读过的每一篇迁移复盘中。
1. 在 render 期间修改 props 或闭包
function Row({ entity }: Props) {
entity.lastSeen = Date.now(); // 在 render 期间发生可变写入
return <span>{entity.name}</span>;
}
Compiler 会拒绝转换这类代码。你需要先修代码。
2. 在 render 期间读取 ref
function Tooltip() {
const ref = useRef<HTMLDivElement>(null);
const width = ref.current?.offsetWidth; // 在 render 主体里读取 ref
return <div ref={ref}>{width}px</div>;
}
把读取操作挪到 effect 或 useLayoutEffect。Compiler 会标记这类问题,但不会替你改写。
3. 遗留 class 组件
class 组件完全不会被编译。如果你还有 React.Component 子类,它们行为和以前一样。对新项目这通常不是问题,但在老代码库里值得明确知道。
4. "use no memo" 逃生舱
有时 Compiler 会判断失误,或者把某个组件改造成“Compiler 安全”所需成本在当前阶段不值得。你可以按函数粒度选择退出:
function ComplicatedLegacyThing() {
"use no memo";
// Compiler 会跳过这个函数,把它当普通 React 处理
...
}
谨慎使用。
每一个
"use no memo"都像是藏在代码库里的性能断崖。把它当作 TODO,而不是永久标记,并在旁边写清楚为什么这段代码暂时不满足 Compiler 安全条件。长期grep这个指令数量,是个很有价值的健康度指标。
大多数团队采用的迁移路径
基于 React 官方文档,以及 Next.js、Expo、TanStack Start 的集成指南,常见推荐流程很短:
- 先升级 React 到支持 Compiler 的版本(React 19 或更高)。
- 先装 ESLint 插件。
eslint-plugin-react-compiler能在没有任何运行时变更的前提下标出 Rules of React 违规,先修这些再碰 Compiler 配置。 - 先启用注解驱动模式。 先编译一两个叶子组件观察效果。
- 再切到推断模式。 让 Compiler 自行判断处理哪些文件。跑测试,并对真实交互做性能剖析。
- 移除手写 memo 化。 Compiler 已接管的
useMemo、useCallback、React.memo可以批量删除。 - 考虑严格模式,在代码库清理干净后启用,这样违规会抛错,而不是仅静默跳过。
跳过前几步、直接做第 5 步的团队,往往会产出一个巨大 PR,并且一开就是几周。第 1、2 步本身就能独立落地并改善代码质量。
仍在争议的问题
十八个月后,社区仍未达成一致的有三件事。
Rules of React 作为可执行契约
Rules of React 在 Compiler 出现前就存在,但那时更像“倡议”。Compiler 把它们变成了构建期边界:一旦违反,组件就会静默退出 memo 化路径。一个声音较大的少数派认为,这等于悄悄施加了比 React 早期承诺更严格的编程模型。反方观点是:这些规则本来就不是真可选项;Compiler 只是把后果显性化。两边都部分成立,而这种张力在那些早于“规则成文化”时代的老库中尤为明显。
"use no memo" 作为永久技术债
这个逃生舱太容易用,这让那些见过代码库多年累积类似标记的人感到担忧。常见类比是 @ts-ignore 和 // eslint-disable:它们是有价值的减压阀,但一旦没人回看,老化速度会很快。另一派则认为这个指令之所以关键,正因为它允许你不做全量重构也能继续交付;把它当永久标记是误用,不是功能本身的问题。如今几乎没人争议的一点是:"use no memo" 的数量是合法且有意义的代码库健康指标。
Compiler 与运行时优化器
对大多数应用,Compiler 已够用。但对列表极重的专业 UI(交易面板、日志查看器、电子表格),像 Million.js 这种基于 block 的协调器仍有可测的优势。两层方案解决的是不同问题:Compiler 降低组件多久重渲染一次,运行时优化器改变每次重渲染有多快。一些团队会两者并用。它们之间的实际交互总体没问题,但还没有形成一份真正“尘埃落定”的文档化共识。我猜这会是未来某篇“定版文章”的主题。
接下来会发生什么
从 React 团队公开信息、Meta 工程文章和活跃 RFC 看,后续方向大致有五个。它们都不是承诺,更像方向信号。
更细粒度的编译控制
当前模式较粗:开、关、注解驱动。团队真正想要的似乎是组件级提示:这个组件激进编译,那个保守编译,这片遗留岛屿永不编译;再加上更好的可视化,让你看到 Compiler 实际做了什么。预计会出现一些能收窄当前“全开/全关”权衡的指令能力。
Compiler 感知的 Server Components
RSC 已经能为客户端岛屿产出更有利于 memo 化的输出。下一步可能是收紧序列化边界:当 Compiler 能证明某值不必跨边界时,浏览器需要 hydration 的负载会更小。真实应用里最大的性能收益常在这里:降低 hydration 成本通常比降低重渲染次数更关键,因为冷启动时用户最先感知到的就是 hydration。
useEvent 走向收敛
那个“长期讨论中的原语”——能读取最新状态、同时保持事件回调稳定的 useEvent——已经在 RFC 里讨论了多年。Compiler 的纯度分析是让其语义可验证的关键,这也是为什么该提案在 Compiler 出现前长期停滞。预计它会以某种形式落地。
React Native
原生视图 diff 的成本通常高于 Web 端协调,所以自动 memo 化在这里的边际价值更大。Expo 已经交付了 Compiler 支持;React Native 本体推进相对更慢。随着“每个组件收益更大”这一事实被更多团队验证,追求对齐的压力在上升。
开发者工具
当前缺的一块,是一个能按组件展示“Compiler 做了什么、为什么这么做”的 DevTools 面板。现在 ESLint 插件能告诉你“哪些被跳过”,但如果有一个“哪些被优化了”的可视化器,会像 RSC 树可视化器那样,把 Compiler 的工作变得可理解。就我所知,这不在任何公开路线图上,但它是最显然的下一步,也是社区插件可能先于 React 团队做出来的那类工具。
我对后续路线的看法
十八个月后,Compiler 的遗产不会是跑分数字,而是它淘汰的一整类 bug。“你漏了一个 useCallback 依赖”已经不再是对话内容。同样,“这个组件要不要 memo 化?”也不再是问题。答案永远是要,而 Compiler 会处理它。
更有意思的问题是:当规则被编译器强制执行后,Rules of React 会变成什么。过去很多年,它们更像“好 React 代码通常会做的事”;现在它们是构建期契约,不满足就会被拒绝。生态里一些最老的库仍在适应。未来一年里,Compiler 故事的重点也许不在 Compiler 本身,而在“当所有东西都被改到 Compiler-safe 后,库生态会变成什么样”。
对新项目,决策很简单:直接开启。对存量项目,决策在于你是要修最老那批代码,还是把 "use no memo" 当作一种承诺装置先交付。这两种都是真实 trade-off,也都值得有意识地做。
在 2026 年再看到 useMemo 或 useCallback 时,可以把它看作现代 JavaScript 里手写 for 循环:通常没问题,偶尔确实必要,但更多时候只是说明这段代码写于更好工具出现之前。
- Introducing React Compiler(React 博客)官方介绍与当前文档,覆盖安装、配置模式和运行时预期。
- React Compiler on GitHub 源码、RFC 与 issue 跟踪,包括配套 ESLint 插件。
- Rules of React Compiler 依赖的契约;排查组件为什么被跳过时可作为检查清单。
- Next.js React Compiler integration 通过 SWC 插件在 Next.js 中启用 Compiler,包括按路由编译模式。
- eslint-plugin-react-compiler 在 Compiler 静默跳过前,先发现 Rules of React 违规的 lint 插件。
术语表(本篇命中)
| 英文术语 | 译文 | 说明 |
|---|---|---|
| React Compiler | React Compiler | React 编译器名称,专有名词保留英文 |
| Rules of React | React 规则(Rules of React) | 编译器依赖的代码约束集合 |
| memoization | memo 化 | 指通过缓存避免重复计算或重复渲染 |
| hydration | Hydration(激活) | 客户端接管服务端渲染输出的过程 |
| brownfield | 棕地项目 | 指带历史包袱的存量系统 |