介绍
多年来,React 开发人员一直使用 useMemo、useCallback、React.memo 和各种状态管理启发式方法手动优化重新渲染。 React Compiler(以前称为 React Forget)完全改变了这种范例。它是一个构建时工具,通过在编译时分析反应依赖关系来自动记忆 React 组件、钩子和值,从而无需手动优化钩子。
手动记忆的问题
考虑一个典型的过滤列表组件:
function UserList({ users, filter }) {
const filtered = useMemo(
() => users.filter(u => u.name.includes(filter)),
[users, filter]
);
const handleClick = useCallback((id) => {
console.log('Clicked:', id);
}, []);
return (
<ul>
{filtered.map(user => (
<UserRow key={user.id} user={user} onClick={handleClick} />
))}
</ul>
);
}
const UserRow = React.memo(({ user, onClick }) => (
<li onClick={() => onClick(user.id)}>{user.name}</li>
));
这是可行的,但它很冗长,容易出错(依赖项数组丢失或不正确),并且很容易忘记。大列表中缺少 useMemo 会导致不必要的重新渲染;不正确的依赖数组会导致陈旧的闭包。
什么是 React 编译器?
React Compiler 是一个构建时插件(Babel / SWC),可以自动理解 React 组件的反应性模型。它会自动转译您的代码以包含记忆逻辑:
// You write this:
function Welcome({ name }) {
const greeting = `Hello, ${name}!`;
return <p>{greeting}</p>;
}
// Compiler outputs effectively this:
function Welcome({ name }) {
const $ = React.cache(/* ... */);
let greeting = $.refresh('greeting', name => `Hello, ${name}!`, [name]);
return $.memo('element', () => <p>{greeting}</p>, [greeting]);
}
编译器会检测哪些值依赖于 props、state 或其他反应值,并且仅在这些依赖项发生变化时重新计算它们。
它是如何运作的
编译器分三个阶段运行:
反应性值检测 — 它遍历抽象语法树 (AST),以识别哪些变量源自 props、state、hooks 或其他反应性源。
依赖图构建 — 它构建连接反应值的依赖图。任何仅源自非反应源的值均被视为静态值。
记忆化代码生成 — 它生成具有内置缓存失效功能的优化 JavaScript。运行时使用类似于信号的细粒度依赖跟踪机制。
编译器遵循的关键规则:
| 规则 | 行为 |
|---|---|
| Props 和 state 始终是反应性的 | 任何派生值都会自动记忆 |
| 非反应常数被提升 | 没有增加记忆开销 |
| 回调默认稳定 | 不需要 useCallback |
| 组件主体优化 | 仅当反应性依赖发生变化时才重新执行 |
与构建工具集成
维特
// vite.config.js
import react from '@vitejs/plugin-react';
export default {
plugins: [
react({
babel: {
plugins: [['babel-plugin-react-compiler', { target: '18' }]],
},
}),
],
};
Next.js
// next.config.js
module.exports = {
experimental: {
reactCompiler: true,
},
};
你可以删除什么
启用 React 编译器后,您不再需要:
useMemo()— 自动值记忆useCallback()— 自动功能稳定性React.memo()— 自动组件记忆(编译器包装渲染输出)- 大多数情况下手动依赖数组
编译器甚至可以优化难以手动处理的模式,例如条件挂钩 - 尽管您仍然应该遵循挂钩规则。
注意事项和最佳实践
虽然编译器可以处理绝大多数情况,但某些情况需要谨慎:
| 警告 | 推荐 |
|---|---|
可变引用 — useRef 突变未被跟踪 | 使用 useState 或触发显式重新渲染 |
| 外部存储 — 值不是来自 React | 包裹在 useSyncExternalStore |
复杂的副作用 — useEffect 具有非反应性依赖 | 如果需要,添加显式依赖 |
| 遗留上下文 — 类组件上下文 | 迁移到现代 useContext |
该编译器对于遵循标准 React 模式的代码库最为有效。高度命令式代码可能带来的好处较少。
结论
React 编译器代表了从手动到自动记忆的根本转变。通过在构建时分析反应性依赖关系,它消除了整个类别的性能错误和样板文件。对于现有项目,启用编译器需要最少的配置;对于新项目,它改变了你对 React 性能的看法——你只需编写声明性组件,然后让编译器处理其余的事情。

