React 编译器是一个新的构建时工具,能够自动优化你的 React 应用。它可以与普通 JavaScript 一起使用,并理解 React 规则,因此你无需重写任何代码即可使用它。

你将学习到

  • React 编译器的功能
  • 编译器入门
  • 增量采用策略
  • 出现问题时进行调试和故障排除
  • 使用 React 库上的编译器

React Compiler 的功能是什么?

🌐 What does React Compiler do?

React 编译器会在构建时自动优化你的 React 应用。React 通常即使不进行优化也足够快,但有时你需要手动对组件和数值进行记忆化,以保持应用的响应速度。这种手动记忆化既繁琐,又容易出错,还会增加额外的维护代码。React 编译器会为你自动补齐这种优化,解除你的心理负担,让你可以专注于构建功能。

🌐 React Compiler automatically optimizes your React application at build time. React is often fast enough without optimization, but sometimes you need to manually memoize components and values to keep your app responsive. This manual memoization is tedious, easy to get wrong, and adds extra code to maintain. React Compiler does this optimization automatically for you, freeing you from this mental burden so you can focus on building features.

React Compiler 之前

🌐 Before React Compiler

不使用编译器时,你需要手动记忆组件和值以优化重新渲染:

🌐 Without the compiler, you need to manually memoize components and values to optimize re-renders:

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>
);
});

注意

这种手动记忆化有一个微妙的 bug,会破坏记忆化:

🌐 This manual memoization has a subtle bug that breaks memoization:

<Item key={item.id} onClick={() => handleClick(item)} />

即使 handleClick 被封装在 useCallback 中,箭头函数 () => handleClick(item) 每次组件渲染时都会创建一个新函数。这意味着 Item 将始终接收到一个新的 onClick 属性,从而破坏了记忆化。

🌐 Even though handleClick is wrapped in useCallback, the arrow function () => handleClick(item) creates a new function every time the component renders. This means that Item will always receive a new onClick prop, breaking memoization.

React 编译器能够正确优化这一点,无论是否使用箭头函数,都能确保 Item 仅在 props.onClick 更改时重新渲染。

🌐 React Compiler is able to optimize this correctly with or without the arrow function, ensuring that Item only re-renders when props.onClick changes.

React Compiler 之后

🌐 After React Compiler

使用 React Compiler,你可以编写相同的代码而无需手动记忆:

🌐 With React Compiler, you write the same code without manual memoization:

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>
);
}

在 React 编译器演示中查看此示例

🌐 See this example in the React Compiler Playground

React Compiler 会自动应用最佳的记忆方式,确保你的应用仅在必要时重新渲染。

🌐 React Compiler automatically applies the optimal memoization, ensuring your app only re-renders when necessary.

深入研究

React Compiler 添加了什么样的记忆?

🌐 What kind of memoization does React Compiler add?

React 编译器的自动记忆化主要集中在提高更新性能(重新渲染现有组件),因此它关注以下两种使用情况:

🌐 React Compiler’s automatic memoization is primarily focused on improving update performance (re-rendering existing components), so it focuses on these two use cases:

  1. 跳过组件的级联重新渲染
    • 重新渲染 <Parent /> 会导致其组件树中的许多组件重新渲染,即使只有 <Parent /> 已经发生了变化
  2. 跳过 React 外部的昂贵计算
    • 例如,在需要该数据的组件或钩子内部调用 expensivelyProcessAReallyLargeArrayOfObjects()

优化重新渲染

🌐 Optimizing Re-renders

React 让你可以将 UI 表达为其当前状态的函数(更具体地说:它们的 props、state 和 context)。在现有实现中,当组件的状态发生变化时,React 会重新渲染该组件_及其所有子组件_——除非你已经使用 useMemo()useCallback()React.memo() 进行了一些形式的手动缓存。例如,在以下示例中,每当 <FriendList> 的状态发生变化时,<MessageButton> 都会重新渲染:

🌐 React lets you express your UI as a function of their current state (more concretely: their props, state, and context). In its current implementation, when a component’s state changes, React will re-render that component and all of its children — unless you have applied some form of manual memoization with useMemo(), useCallback(), or React.memo(). For example, in the following example, <MessageButton> will re-render whenever <FriendList>’s state changes:

function FriendList({ friends }) {
const onlineCount = useFriendOnlineCount();
if (friends.length === 0) {
return <NoFriends />;
}
return (
<div>
<span>{onlineCount} online</span>
{friends.map((friend) => (
<FriendListCard key={friend.id} friend={friend} />
))}
<MessageButton />
</div>
);
}

See this example in the React Compiler Playground

React 编译器会自动应用等同于手动记忆化的操作,确保应用中只有相关部分会随着状态变化而重新渲染,这有时被称为“细粒度响应性”。在上述示例中,React 编译器确定即使 friends 发生变化,<FriendListCard /> 的返回值仍然可以被重用,并且可以避免重新创建此 JSX,同时在计数变化时避免重新渲染 <MessageButton>

🌐 React Compiler automatically applies the equivalent of manual memoization, ensuring that only the relevant parts of an app re-render as state changes, which is sometimes referred to as “fine-grained reactivity”. In the above example, React Compiler determines that the return value of <FriendListCard /> can be reused even as friends changes, and can avoid recreating this JSX and avoid re-rendering <MessageButton> as the count changes.

昂贵的计算也会被记忆化

🌐 Expensive calculations also get memoized

React Compiler 还可以自动记忆渲染过程中的复杂计算:

🌐 React Compiler can also automatically memoize expensive calculations used during rendering:

// **Not** memoized by React Compiler, since this is not a component or hook
function expensivelyProcessAReallyLargeArrayOfObjects() { /* ... */ }

// Memoized by React Compiler since this is a component
function TableContainer({ items }) {
// This function call would be memoized:
const data = expensivelyProcessAReallyLargeArrayOfObjects(items);
// ...
}

See this example in the React Compiler Playground

然而,如果 expensivelyProcessAReallyLargeArrayOfObjects 真的是一个昂贵的函数,你可能需要考虑在 React 外部实现它自己的记忆化,因为:

🌐 However, if expensivelyProcessAReallyLargeArrayOfObjects is truly an expensive function, you may want to consider implementing its own memoization outside of React, because:

  • React Compiler 仅记忆 React 组件和钩子,而不是每个函数
  • React Compiler 的记忆不会在多个组件或钩子之间共享

所以如果 expensivelyProcessAReallyLargeArrayOfObjects 被用于许多不同的组件,即使传递下来的完全是相同的项目,那次昂贵的计算也会被重复执行。我们建议先进行 性能分析,以查看它是否真的那么昂贵,然后再使代码变得更复杂。

我应该尝试一下编译器吗?

🌐 Should I try out the compiler?

我们鼓励每个人开始使用 React 编译器。虽然编译器今天仍然是 React 的可选附加功能,但将来某些功能可能需要编译器才能完全工作。

🌐 We encourage everyone to start using React Compiler. While the compiler is still an optional addition to React today, in the future some features may require the compiler in order to fully work.

使用安全吗?

🌐 Is it safe to use?

React 编译器现在已经稳定,并且在生产环境中进行了广泛的测试。虽然它已经在像 Meta 这样的公司中投入生产使用,但将该编译器部署到你的应用生产环境中将取决于你的代码库的健康状况以及你遵循 React 规则 的情况。

🌐 React Compiler is now stable and has been tested extensively in production. While it has been used in production at companies like Meta, rolling out the compiler to production for your app will depend on the health of your codebase and how well you’ve followed the Rules of React.

支持哪些构建工具?

🌐 What build tools are supported?

React 编译器可以安装在多个构建工具中,例如 Babel、Vite、Metro 和 Rsbuild。

🌐 React Compiler can be installed across several build tools such as Babel, Vite, Metro, and Rsbuild.

React Compiler 主要是一个轻量级的 Babel 插件封装器,围绕核心编译器设计,其目的是与 Babel 本身解耦。虽然编译器的初始稳定版本仍然主要作为 Babel 插件存在,但我们正在与 swc 和 oxc 团队合作,为 React Compiler 构建一流的支持,这样你将来就不必在构建管道中再添加 Babel。

🌐 React Compiler is primarily a light Babel plugin wrapper around the core compiler, which was designed to be decoupled from Babel itself. While the initial stable version of the compiler will remain primarily a Babel plugin, we are working with the swc and oxc teams to build first class support for React Compiler so you won’t have to add Babel back to your build pipelines in the future.

Next.js 用户可以通过使用 v15.3.1 及更高版本启用由 swc 调用的 React 编译器。

🌐 Next.js users can enable the swc-invoked React Compiler by using v15.3.1 and up.

我应该如何处理 useMemo、useCallback 和 React.memo?

🌐 What should I do about useMemo, useCallback, and React.memo?

默认情况下,React 编译器会根据其分析和启发式方法对你的代码进行记忆化。在大多数情况下,这种记忆化的精确度会与你自己编写的代码一样精确,甚至更高。

🌐 By default, React Compiler will memoize your code based on its analysis and heuristics. In most cases, this memoization will be as precise, or moreso, than what you may have written.

然而,在某些情况下,开发者可能需要对记忆化拥有更多的控制。useMemouseCallback 钩子可以继续与 React 编译器一起使用,作为一种应急手段来控制哪些值被记忆化。一个常见的使用场景是,如果一个记忆化的值被用作副作用的依赖,以确保即使其依赖没有实质性变化,副作用也不会重复触发。

🌐 However, in some cases developers may need more control over memoization. The useMemo and useCallback hooks can continue to be used with React Compiler as an escape hatch to provide control over which values are memoized. A common use-case for this is if a memoized value is used as an effect dependency, in order to ensure that an effect does not fire repeatedly even when its dependencies do not meaningfully change.

对于新代码,我们建议依赖编译器进行记忆化,并在需要时使用 useMemo/useCallback 以实现精确控制。

🌐 For new code, we recommend relying on the compiler for memoization and using useMemo/useCallback where needed to achieve precise control.

对于现有代码,我们建议要么保持现有的记忆化(移除它可能会改变编译结果),要么在移除记忆化之前仔细测试。

🌐 For existing code, we recommend either leaving existing memoization in place (removing it can change compilation output) or carefully testing before removing the memoization.

尝试 React Compiler

🌐 Try React Compiler

本节将帮助你开始使用 React Compiler 并了解如何在你的项目中有效地使用它。

🌐 This section will help you get started with React Compiler and understand how to use it effectively in your projects.

其他资源

🌐 Additional resources

除了这些文档外,我们还建议查看 React 编译器工作组 获取有关编译器的更多信息和讨论。

🌐 In addition to these docs, we recommend checking the React Compiler Working Group for additional information and discussion about the compiler.