React Compiler
本页面将为你介绍新的React Compiler,以及如何成功试用。
你将会学到
-开始使用React Compiler
-安装React Compiler和ESLint插件
-疑难解答
注意:
React Compiler是一个处于RC阶段的新的编译器,我们将其开源以获取社区反馈。现在我们建议所有人都是用编译器。
最新的RC版本发布于@rc标签,每日实验版本发布于@experimental标签。
React Compiler是一个新编译器,我们将其开源以获得社区的早期反馈。它是一个仅在构建时使用的工具,可以自动优化你的React应用程序。它可以与纯JavaScript一起使用,并且了解React规则,因此你无需重写任何代码即可使用它。
编译器还包括一个ESLint插件,可以在你的编辑器中直接显示编译器的分析结果。我们强烈建议大家使用linter。不过linter并不需要安装编译器,因此即是你还没有准备好尝试编译器也可以使用它。
编译器目前处于beta阶段,并且可以在React 17+应用程序和库上使用。安装方式如下:
| 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 19,请参考此内容以获得进一步说明。
编译器是做什么的?
为了优化应用程序,React Compiler会自动对你的代码进行记忆化处理。你可能已经熟悉了像useMemo、useCallback和React.memo这样的API,通过这些API,你可以告诉React,如果输入没有发生变化,应用程序的某些部分不需要重新计算,从而减少更新时的工作量。虽然这些功能很强大,但很容易忘记应用记忆化护着错误地应用它们。这可能会导致更新效率低下,因为React必须检查UI中没有任何意义的更改的部分。
编译器利用其对JavaScript和React规则的了解,自动对组件和钩子中的值或值组进行记忆化。如果它检测到规则的破坏,它将自动跳过那些组件或钩子,并继续安全地编译其他代码。
注意:React Compiler可以静态检测React规则何时被破坏,并安全地选择不优化受影响的组件或钩子。编译器没有必要对代码库进行100%的优化。
如果你的代码库已经非常好地进行了记忆化处理,你可能不会指望通过编译器看到主要的性能改进。然而,在实践中,手动正确记忆化导致性能问题的依赖关系是很棘手的。
React Compiler添加了什么样的记忆?
React Compiler的初始版本主要专注于改善更新性能(重新渲染现有组件),因此它专注于以下两种用例:
1.跳过组件的级联重新渲染
-重新渲染<Parent />会导致其组件树中的许多组件重新渲染,即使只有<Parent />发生了变化
2.从React外部跳过昂贵计算
-例如,在需要该数据的组件或钩子内部调用
expensivelyProcessAReallyLargeArrayOfObjects()
优化重新渲染
React允许你将你的UI表达为它们当前状态的函数(更具体地说:它们的属性、状态和上下文)。在当前的实现中,当组件的状态发生变化时,React将重新渲染该组件及其所有子组件—除非你使用useMemo()、useCallback()或React.memo()应用了某种形式的手动记忆。例如,在一下示例中,每当<FriendList>的状态发生变化时,<MessageButton>将重新呈现:
| 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 Playground中查看此示例
React Compiler会自动应用等效的手动记忆,确保只有应用的相关部分在状态发生变化时重新渲染,这有时被称为“细颗粒度反应”。在上面的例子中,React Compiler确定<FriendListCard />的返回值即使在friends发生变化时也可以重用,并且可以避免重新创建此JSX,并避免在onlineCount变化时重新渲染<MessageButton>。
昂贵计算也会被记忆
编译器还可以自动记忆渲染过程中使用的昂贵计算:
| // 由于这不是组件或钩子,React Compiler不会进行记忆 function expensivelyProcessAReallyLargeArrayOfObjects() {/*...*/} // 由React Compiler进行了记忆化,因为这是一个组件 function TableContainer({ items }) { // 这个函数调用将被记忆: const data = expensivelyProcessAReallyLargeArrayOfObjects(itmes); // ... } |
在React Compiler Playground中查看此时里
但是,如果expensivelyProcessAReallyLargeArrayOfObjects确实是一个昂贵的函数,你可能需要考虑在React之外实现它自己的记忆,因为:
-React Compiler只记住React组件和钩子,而不是每个函数
-React Compiler的记忆不会在多个组件或钩子之间共享
因此,如果在许多不同的组件中使用expensivelyProcessAReallyLargeArrayObObjects,即是传递相同的items,那昂贵的计算也会被重复运行。我们建议优先进行性能分析,看看是否真那么昂贵,然后再使代码更加复杂。
我应该尝试一下编译器吗?
请注意,编译器处于RC阶段,并已在生产中进行了广泛测试。虽然它已经在Meta等公司的生产环境中使用过,但将编译器应用于你的应用程序生产环境将取决于你的代码库的健康状况以及你是否遵循了React的规则
你现在不必急着使用编译器。在采用它之前等到它达到稳定版本是可以的。
开始
除了这些文档之外,我们还建议查看React Compiler工作组,以获取有关编译器的更多信息和讨论。
安装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规则的行为。当它这样做时,这意味着编译器跳过了优化该组件或钩子。这是完全可以的,编译器可以恢复并继续优化代码库中的其他组件。
注意:
你必须立即修复所有的违反ESLint规则的代码。你可以按照自己的节奏来处理它们,以增加被优化的组件和钩子的数量,但在你可以使用编译器之前并不需要修复所有问题。
将编译器应用到你的代码库
编译器旨在编译遵循React规则的功能组件或钩子。它还可以处理违反这些规则的代码,通过跳过这些组件或钩子来终止执行。然而,由于JavaScript的灵活性,编译器无法捕捉到每一个可能的违规行为,可能回出现错误的负面编译:也就是说,编译器可能会意外地编译出一个违反React规则的组件或钩子,这可能导致未定义的行为。
因此,要在现有项目中成功采用编译器,我们建议你先在项目代码中的一个小目录中运行它。你可以通过将编译器设置为仅在一组特定的目录上运行来执行此操作:
| const ReactCompilerConfig = { sources: (filename) => { return filename.indexOf('src/path/to/dir') != -1; } } |
当你对编译器更有信息时,你也可以将覆盖范围扩展到其他目录,并逐渐将其推出到整个应用程序。
新项目
如果你正在启动一个新项目,你可以在整个代码库上启用编译器,这是默认行为。
在React17或18中使用React Compiler
React Compiler与React19 RC 配合使用效果最佳。如果你无法升级,可以安装额外的react-compiler-runtime包来编译代码并在19之前的版本上运行。但请注意,支持的最低版本是17.
| npm install react-compiler-runtime@rc |
你还应该在编译器配置中添加正确的target值,为你所使用的的React大版本。
| // babel.config.js const ReactCompilerConfig = { target: '18' // '17' | '18' | '19' }; module.exports = function() { return { plugins: [ ['babel-plugin-react-compiler', ReactCompilerConfig], ], }; }; |
在库中使用Compiler
React Compiler还可用于编译库。由于React Compiler需要在代码转换之前的源码上运行,因此应用程序无法使用pipeline来编译所使用的的库。因此我们建议库维护人员使用编译器独立编译和测试他们的库,并将编译后的代码发布到npm。
由于库的代码是预编译的,因此用户无序启用Compiler即可从编译器的自动记忆中收益。如果库的target不是React 19,请指定一个最小的target并且将react-compiler-runtime添加为直接依赖。这个运行时包将根据应用程序的版本使用正确的API实现,并在必要时填充缺失的API。
库代码通常需要更复杂的模式和脱围机制。因此我们建议你进行足够测试,以便发现在库中使用编译器时可能出现的任何问题。对于任何发现的问题都可以使用’use no memo’指令来选择退出特定组件或Hook的自动记忆化。
与应用程序类似,无需100%编译组件或Hook就可以看到编译器带来的好处。一个好的起点可能是确定库中对性能最敏感的部分,并确保它们没有违反React规则,你可以通过使用eslint-plugin-react-compiler来完成。
用法
Babel
| npm install babel-plugin-react-compiler@rc |
编译器包含一个Babel插件,你可以在构建流水线中使用它来运行编译器。
安装后,请将其添加到你的Babel配置中。请注意,编译器必须首先在流水线中运行。
| // babel.cofig.js const ReactCompilerConfig = { /* ... */ }; module.exports = function () { return { plugins: [ ['babel-plugin-react-compiler', ReactCompilerConfig], // 必须首先运行! ], }; }; |
babel-plugin-react-compiler应该在其他Babel插件之前运行,因为编译器需要输入源信息进行声音分析。
Vite
如果你使用Vite,你可以将插件添加到vite-plugin-react中:
| // babel.cofig.js const ReactCompilerConfig = { /* ... */ }; export default defineConfig( () => { return { plugins: [ react( babel: { plugins: [ ["babel-plugin-react-compiler", ReactCompilerConfig], ], }), ], // ... }, }); |
Next.js
请参考Next.js文档来了解更多信息。
Remix
安装vite-plugin-babel,并将编译器的Babel插件添加到其中:
| npm install vite-plugin-babel |
| // vite.config.js import babel from "vite-plugin-babel"; const ReactCompilerConfig = { /* ... */ } export default defineConfig({ plugins: [ remis({ /* ... */}), babel({ filter: /\.[jt]sx?$/, babelConfig: [ presets: ["@babel//preset-typescript"], // 如果你使用TypeScript plugins: [ ["babel-plugin-react-compiler", ReactCompilerConfig], ], ], }), ], }); |
Webpack
由社区提供的webpack loader可以在这里找到。
Expo
请参考Expo文档应用程序启用和使用React Compiler。
Metro(React Native)
React Native通过Metro使用Babel,因此请参考使用Babel部分的安装说明。
Rspack
请参考Rsbuild文档以在Rsbuild应用程序中启用和使用React Compiler。
疑难解答
请先在React Compiler Playground上创建一个最小的可复现问题,并将其包含在你的错误报告中。你可以在facebook/react仓库中提交issue。
你也可以通过申请成为会员,在React Compiler工作组中提供反馈意见。请查看README以获取更多加入详情。
编译器假设什么?
React Compiler假设你的代码:
- 是有效的,语义化的JavaScript
- 在访问可空/可选值和属性之前,测试他们是否已定义(例如,如果使用TypeScript,则启用strictNullChecks),即:if (object.nullableProperty) { object.nullableProperty.foo }或者使用可选链object.nullableProperty?.foo
- 遵循React规则
React Compiler可以静态验证React的许多规则并且在检测到错误时安全地跳过编译。要查看错误,我们建议同时安装eslint-plugin-react-compiler。
我如何知道我的组件已被优化?
React DevTools (v5.0+)和React Native DevTools内置支持React Compiler,并会在已被编译器优化的组件旁边显示“Memo”徽章。
编译后某些内容无法正常工作
如果你安装了eslint-plugin-react-compiler,编译器将在你的编译器中显示任何违反React规则的情况。当它这样做时,意味着编译器跳过了对该组件或钩子的优化。这完全没问题,并且编译器可以恢复并继续优化你代码库中的其他组件。你不必立即修复所有的违反ESLint规则的代码。你可以按照自己的节奏来处理它们,以增加被优化的组件和钩子的数量。
然而,由于JavaScript的灵活和动态性质,不可能全面检测到所有情况。在这些情况下,可能会出现错误和未定义的行为,例如无限循环。
如果你的应用在编译后无法正常工作,并且你没有看到任何ESLint错误,编译器可能错误地编译了你的代码。为了确认这一点,尝试通过积极选择你认为可能相关的任何组件或钩子,并通过”use no memo”指令退出优化来解决问题。
| function SuspiciousComponent() { "use no memo"; // 选择不让此组件由React Compiler 进行编译 } |
注意:
“use no memo”是一个临时的逃避机制,它允许你选择不让组件和钩子由React Compiler进行编译。此指令不像例如”use client”那样长期存在。
除非绝对必要,否则不建议使用这个指令。一旦你选择退出一个组件或钩子,它将永久退出,直到指令被移除。这意味着即使你修复了代码,编辑器仍然会跳过编译它,除非你移除指令。
当你修复错误时,请确认删除退出指令是否会使问题重新出现。然后使用React Compiler Playground与我们分享一个错误报告(你可以尝试将其减少到一个小的重现,或者如果是开源代码,你也可以直接粘贴整个源代码),这样我们就可以识别并帮助解决问题。
866

被折叠的 条评论
为什么被折叠?



