React 编译器

发布于:2025-05-30 ⋅ 阅读:(22) ⋅ 点赞:(0)

在这里插入图片描述

🤖 作者简介:水煮白菜王,一位前端劝退师 👻
👀 文章专栏: 前端专栏 ,记录一下平时在博客写作中,总结出的一些开发技巧和知识归纳总结✍。
感谢支持💕💕💕
本文内容参考自 React文档 - React Compiler 内容进行记录和整理✍

React 编译器

本文将为你介绍 React Compiler 以及如何成功试用它

本文您进入React 编译器将学习
编译器入门
安装编译器和 ESLint 插件
进行故障排除

注意
React Compiler 是目前在 RC 中的一个新编译器,React已经开源了它以从社区获得反馈。现在建议大家试用编译器并提供反馈。
最新的 RC 版本可以通过标签找到,每日实验版本可以使用 .@rc@experimental

React Compiler 是React开源的一个新的编译器,用于从社区获取反馈。它是一个仅限构建时的工具,可自动优化您的 React 应用程序。它与纯 JavaScript 一起工作,并且理解 React 的规则,因此你不需要重写任何代码来使用它。

eslint-plugin-react-hooks 还包括一个 ESLint 规则,该规则直接在编辑器中显示来自编译器的分析。React强烈建议大家立即使用 Linter。Linter 不需要您安装编译器,因此即使您还没有准备好试用编译器,也可以使用它。

该编译器目前作为 发布,可以在 React 17+ 应用程序和库上试用。要安装 RC:rc
npm

npm install -D babel-plugin-react-compiler@rc eslint-plugin-react-hooks@^6.0.0-rc.1

yarn

yarn add -D babel-plugin-react-compiler@rc eslint-plugin-react-hooks@^6.0.0-rc.1

编译器是做什么的?

为了优化应用程序,React Compiler 会自动记住你的代码。您现在可能熟悉通过 API 进行记忆化,例如 、 和 。通过这些 API,你可以告诉 React 如果它们的输入没有改变,你的应用程序的某些部分就不需要重新计算,从而减少了更新的工作。虽然功能强大,但很容易忘记应用记忆化或错误地应用它们。这可能会导致更新效率低下,因为 React 必须检查没有任何有意义的更改的 UI 部分。useMemouseCallbackReact.memo

编译器利用其 JavaScript 和 React 规则的知识来自动记住组件和 hook 中的值或值组。如果它检测到规则的破坏,它将自动跳过这些组件或 hook,并继续安全地编译其他代码。

注意

React Compiler 可以静态检测何时违反 React 规则,并安全地选择退出仅优化受影响的组件或钩子。编译器没有必要优化 100% 的代码库

如果您的代码库已经很好地记住了,您可能不希望看到编译器的性能有重大改进。但是,在实践中,记住导致性能问题的正确依赖项是很棘手的。

深入探究

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

React Compiler 的初始版本主要专注于提高更新性能(重新渲染现有组件),因此它专注于以下两个用例:
1.跳过组件的级联重新渲染

  • 重新渲染会导致其组件树中的许多组件重新渲染,即使只有更改<Parent /><Parent />

2.跳过 React 外部的昂贵计算

  • 例如,在需要该数据的组件或 hook 内部调用expensivelyProcessAReallyLargeArrayOfObjects()
优化重新渲染

React 允许你将你的 UI 表示为当前状态的函数(更具体地说:它们的 props、state 和 context)。在当前的实现中,当组件的状态发生变化时,React 将重新渲染该组件及其所有子组件 — 除非你使用 、 或 应用了某种形式的手动记忆。例如,在下面的示例中,每当 的状态发生变化时,都会重新渲染:useMemo() useCallback() React.memo() <MessageButton><FriendList>

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

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

昂贵的计算也会被记忆化

编译器还可以自动 memoize 渲染期间使用的昂贵计算:

// **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);
 // ...
}

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

  • React 编译器只记住 React 组件和钩子,而不是每个函数
  • React Compiler 的记忆不会在多个组件或 hook 之间共享

因此,如果用于许多不同的组件,即使传递了完全相同的项目,也会重复运行昂贵的计算。建议先进行分析,看看它是否真的那么昂贵,然后再使代码变得更复杂。expensivelyProcessAReallyLargeArrayOfObjects

开始使用

安装 eslint-plugin-react-hooks

React Compiler 还为 ESLint 插件提供支持。你可以通过安装 eslint-plugin-react-hooks@^6.0.0-rc.1 来试用。

npm install -D eslint-plugin-react-hooks@^6.0.0-rc.1

ESLint 插件将在编辑器中显示任何违反 React 规则的行为。当它这样做时,这意味着编译器跳过了优化该组件或 hook。这是完全可以的,编译器可以恢复并继续优化代码库中的其他组件。

你不必立即修复所有 ESLint 冲突。您可以按照自己的节奏解决它们,以增加要优化的组件和 hook 的数量,但不需要在使用编译器之前修复所有问题。

将编译器推出到您的代码库

现有项目

编译器旨在编译遵循 React 规则的功能组件和 hook。它还可以通过救助 (跳过) 这些组件或 hook 来处理违反这些规则的代码。然而,由于 JavaScript 的灵活性,编译器无法捕获所有可能的违规,并且可能会以漏报进行编译:也就是说,编译器可能会意外编译一个违反 React 规则的组件/钩子,这可能导致未定义的行为。

因此,要在现有项目中成功采用编译器,建议先在产品代码中的小目录上运行它。您可以通过将编译器配置为仅在一组特定的目录上运行来执行此作:

const ReactCompilerConfig = {
  sources: (filename) => {
    return filename.indexOf('src/path/to/dir') !== -1;
  },
};

您对推出编译器更有信心时,您也可以将覆盖范围扩展到其他目录,并慢慢将其扩展到整个应用程序。

新项目

如果要启动新项目,则可以在整个代码库上启用编译器,这是默认行为。

在 React 17 或 18 中使用 React 编译器

React 编译器与 React 19 RC 配合得最好。如果您无法升级,您可以安装额外的软件包,这将允许编译后的代码在 19 之前的版本上运行。但是,请注意,支持的最低版本为 17。react-compiler-runtime

npm install react-compiler-runtime@rc

你还应该将 correct 添加到你的编译器配置中,其中 是你目标的 React 的主要版本:target target

// babel.config.js
const ReactCompilerConfig = {
  target: '18' // '17' | '18' | '19'
};

module.exports = function () {
  return {
    plugins: [
      ['babel-plugin-react-compiler', ReactCompilerConfig],
    ],
  };
}

在库上使用编译器

React Compiler 也可以用于编译库。因为 React Compiler 需要在任何代码转换之前在原始源代码上运行,所以应用程序的构建管道不可能编译它们使用的库。因此,建议库维护者使用编译器独立编译和测试他们的库,并将编译后的代码发送到 npm。

由于您的代码是预编译的,因此您的库的用户不需要启用编译器即可从应用于您的库的自动记忆化中受益。如果你的库面向尚未在 React 19 上运行的应用程序,请指定最小目标并将 react-compiler-runtime 添加为直接依赖项。运行时包将根据应用程序的版本使用正确的 API 实现,并在必要时对缺少的 API 进行 polyfill 填充。

库代码通常需要更复杂的模式和转义舱口的使用。因此,建议您确保进行足够的测试,以便识别在库上使用编译器可能出现的任何问题。如果你发现任何问题,你可以随时使用 'use no memo' 指令选择退出特定的组件或 hook。

与 app 类似,你不需要完全编译 100% 的组件或 hook 就能看到你的库里的好处。一个好的起点可能是确定库中对性能最敏感的部分,并确保它们不会违反 React 规则,你可以用它来识别。eslint-plugin-react-compiler

用法

Babel

npm install babel-plugin-react-compiler@rc

编译器包括一个 Babel 插件,您可以在构建管道中使用它来运行编译器。

安装后,将其添加到你的 Babel 配置中。请注意,编译器首先在管道中运行至关重要:

// babel.config.js
const ReactCompilerConfig = { /* ... */ };

module.exports = function () {
  return {
    plugins: [
      ['babel-plugin-react-compiler', ReactCompilerConfig], // must run first!
      // ...
    ],
  };
};

babel-plugin-react-compiler应该在其他 Babel 插件之前先运行,因为编译器需要输入源信息进行声音分析。

Vite

如果你使用 Vite,你可以将插件添加到 vite-plugin-react 中:

// vite.config.js
const ReactCompilerConfig = { /* ... */ };

export default defineConfig(() => {
  return {
    plugins: [
      react({
        babel: {
          plugins: [
            ["babel-plugin-react-compiler", ReactCompilerConfig],
          ],
        },
      }),
    ],
    // ...
  };
});

Next.js :

参阅 Next.js 文档

Remix

安装 ,并向其添加编译器的 Babel 插件:vite-plugin-babel

npm install vite-plugin-babel
// vite.config.js
import babel from "vite-plugin-babel";

const ReactCompilerConfig = { /* ... */ };

export default defineConfig({
  plugins: [
    remix({ /* ... */}),
    babel({
      filter: /\.[jt]sx?$/,
      babelConfig: {
        presets: ["@babel/preset-typescript"], // if you use TypeScript
        plugins: [
          ["babel-plugin-react-compiler", ReactCompilerConfig],
        ],
      },
    }),
  ],
});

Webpack

社区 webpack loader已提供。

故障 排除

要报告问题,请首先在 React Compiler Playground 上创建一个最小重现,并将其包含在你的错误报告中。您可以在 facebook/react 存储库中打开问题。

你也可以通过申请成为成员在 React 编译器工作组中提供反馈。有关 加入的更多详细信息,请参阅 README

编译器假定什么?

React 编译器假定你的代码:

  1. 是有效的语义 JavaScript。
  2. 测试在访问可空/可选值和属性之前是否定义它们(例如,如果使用 TypeScript,则通过启用 strictNullChecks ),即或使用 optional-chaining 。if (object.nullableProperty) { object.nullableProperty.foo }object.nullableProperty?.foo
  3. React 的规则

React Compiler 可以静态验证 React 的许多规则,并在检测到错误时安全地跳过编译。要查看错误,建议同时安装 。eslint-plugin-react-compiler

如何知道我的组件已优化?

React DevTools (v5.0+)React Native DevTools内置了对 React 编译器的支持,并将在编译器优化的组件旁边显示 “Memo ✨ ” 徽章。

编译后某些内容无法正常工作

如果你安装了 eslint-plugin-react-compiler,编译器会在你的编辑器中显示任何违反 React 规则的行为。当它这样做时,这意味着编译器跳过了优化该组件或 hook。这是完全可以的,编译器可以恢复并继续优化代码库中的其他组件。你不必立即修复所有 ESLint 冲突。您可以按照自己的节奏解决这些问题,以增加要优化的组件和 hook 的数量。

然而,由于 JavaScript 的灵活和动态特性,不可能全面检测所有情况。在这些情况下,可能会出现错误和未定义的行为,例如无限循环。

如果你的 app 在编译后无法正常工作,并且你没有看到任何 ESLint 错误,则编译器可能错误地编译了你的代码。为了确认这一点,请尝试通过 “use no memo” 指令主动选择退出您认为可能相关的任何组件或 hook,从而使问题消失。

function SuspiciousComponent() {
  "use no memo"; // opts out this component from being compiled by React Compiler
  // ...
}

注意
“use no memo”
"use no memo"是一个临时的转义舱口,允许你选择退出 React 编译器编译的组件和钩子。这个指令并不意味着像 use client 那样长寿。

除非绝对必要,否则不建议使用该指令。一旦你选择退出一个组件或 hook,它就会永远选择退出,直到该指令被删除。这意味着,即使您修复了代码,编译器仍将跳过编译它,除非您删除该指令。
#如果你觉得这篇文章对你有帮助,请点赞 👍、收藏 👏 并关注我!👀

当您使错误消失时,请确认删除 opt out 指令会使问题再次出现。然后使用 React Compiler Playground 与React共享错误报告(您可以尝试将其简化为一个小的复制,或者如果它是开源代码,您也可以直接粘贴整个源代码),以便官方识别并帮助解决问题。

如果你觉得这篇文章对你有帮助,请点赞 👍、收藏 👏 并关注我!👀

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到