React Compiler 自动优化
本教程共 50 篇 · 第 46 篇 · 更新于 2026-07-29 · 约 8 分钟阅读
46. React Compiler 自动优化
本节目标:理解 React Compiler 如何替你自动记忆化,减少手动优化的需要。
为什么需要编译器
React 默认性能已经够快。但有些场景你需要手动”记忆化”来避免不必要的重渲染:
import { useMemo, useCallback, memo } from 'react';
const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
const processedData = useMemo(() => {
return expensiveProcessing(data);
}, [data]);
const handleClick = useCallback((item) => {
onClick(item.id);
}, [onClick]);
return (
<div>
{processedData.map(item => (
<Item key={item.id} onClick={() => handleClick(item)} />
))}
</div>
);
});
问题来了——handleClick 确实被 useCallback 包裹了。
但 () => handleClick(item) 每次渲染还是新建一个箭头函数,memo 的优化就这么被破了。
手动记忆化既繁琐又容易出错。React 编译器帮你自动搞定。
编译器做了什么
React Compiler 是构建时工具。它在编译阶段分析你的代码,自动插入等效的记忆化逻辑。
编译前:
function ExpensiveComponent({ data, onClick }) {
const processedData = expensiveProcessing(data);
const handleClick = (item) => {
onClick(item.id);
};
return (
<div>
{processedData.map(item => (
<Item key={item.id} onClick={() => handleClick(item)} />
))}
</div>
);
}
编译后(伪代码):
import { c as _c } from "react/compiler-runtime";
function ExpensiveComponent({ data, onClick }) {
const $ = _c(2); // 分配缓存槽
let processedData, handleClick;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
processedData = expensiveProcessing(data);
$[0] = processedData; // 缓存计算结果
} else {
processedData = $[0];
}
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
handleClick = (item) => { onClick(item.id); };
$[1] = handleClick; // 缓存函数
} else {
handleClick = $[1];
}
// ...渲染 JSX
}
编译器能正确处理箭头函数的问题,比手写更可靠。
安装与配置
安装
npm install -D babel-plugin-react-compiler@latest
Vite 配置(推荐)
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
}),
],
});
NoteReact 编译器必须在 Babel 插件管道中第一个运行,否则无法正确分析源码。
验证是否生效
打开 React DevTools,被优化的组件旁边会显示 “Memo ✨” 徽章。
编译器能优化什么
编译器主要针对两类场景:
1. 跳过组件的级联重渲染
父组件 state 变了,所有子组件默认都会重渲染。
编译器能分析出”只有依赖的部分需要重渲染”,自动跳过无关子树。
function FriendList({ friends }) {
const onlineCount = useFriendOnlineCount();
return (
<div>
<span>{onlineCount} 人在线</span>
{friends.map(friend => (
<FriendCard key={friend.id} friend={friend} />
))}
<MessageButton />
</div>
);
}
编译后,friends 变化时 <MessageButton> 不会重新渲染。
onlineCount 变化时 <FriendCard> 列表不会被重建。
2. 缓存昂贵计算
function TableContainer({ items }) {
// 这个调用会被自动缓存
const data = expensivelyProcess(items);
// ...
}
Warning编译器只对 React 组件和 Hook 做记忆化,不处理普通函数。跨组件共享的缓存也不会自动处理。
不需要编译器的时候
某些场景下你想跳过编译。用 "use no memo" 指令:
function ProblematicComponent() {
"use no memo";
// 这里的代码不会被编译器优化
}
这应该是临时方案。找到根因后应该移除指令,让编译器处理。
与现有 useMemo 的关系
编译器不是来替代 useMemo 的——至少现在不是。
推荐做法:
- 新项目:信任编译器,少写甚至不写
useMemo/useCallback - 老项目:保留现有的记忆化,移除它可能改变编译器的优化结果
- 精确控制场景:如果 memoized 值被用作 effect 依赖,保留
useMemo
// 这个场景建议保留 useMemo
const options = useMemo(() => ({
url: props.url,
method: 'POST'
}), [props.url]);
useEffect(() => {
fetchData(options);
}, [options]); // options 引用稳定,effect 不会重复触发
ESLint 插件
React 编译器附带一条 ESLint 规则,帮你识别无法优化的代码:
npm install -D eslint-plugin-react-hooks@latest
ESLint 会标出违反 React 规则的组件。被标记的组件会被编译器跳过。
你不需要一次修完所有警告。按自己的节奏逐个修复即可。
渐进式采用
如果一次性全量上线不放心,有三种渐进策略:
1. 按目录编译
// babel.config.js
module.exports = {
overrides: [
{
test: './src/modern/**/*.{js,jsx,ts,tsx}',
plugins: ['babel-plugin-react-compiler']
}
]
};
2. 选择加入模式
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
compilationMode: 'annotation'
}]
]
};
需要优化的组件手动加 "use memo":
function TodoList({ todos }) {
"use memo";
const sorted = todos.slice().sort();
// ...
}
3. 运行时功能开关
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
gating: {
source: 'ReactCompilerFeatureFlags',
importSpecifierName: 'isCompilerEnabled'
}
}]
]
};
可以做 A/B 测试,按用户分组逐步放量。
调试技巧
如果编译后行为不对:
- 在可疑组件加
"use no memo",看问题是否消失 - 检查是否违反 React 规则(用 ESLint)
- 尝试移除手动记忆化,看是否是两种优化冲突
编译器错误很少见。大多数问题是运行时违反 React 规则导致的。
Tip在 React DevTools 里看”Memo ✨“徽章。没出现说明该组件被编译器跳过了。
小结
- React Compiler 在构建时自动插入记忆化代码
- 目标是减少手动使用
useMemo、useCallback、React.memo的需要(某些场景仍需手动优化) - Vite 配置加 Babel 插件即可启用,React DevTools 验证
- 渐进式采用支持按目录、选择加入、运行时开关三种策略
- 调试时用
"use no memo"定位问题组件