Using the experimental React Compiler

Alex O'Callaghan

使用实验性的 React Compiler

原文由 Alex O'Callaghan 发布,订阅该博客

实验性的 React Compiler 已经开源,需要使用 React 19 的 Beta 版本。该编译器基于对 JavaScript 语义和 Rules of React 的理解,自动进行记忆化以提升性能。这有望取代通过 useMemouseCallbackReact.memo 进行手动记忆化的需求。

我决定尝试把这个编译器接入我们的微前端壳应用。它负责将多个微前端组合成一个完整的应用,因此性能的提升可能会对整个平台的用户体验产生很大影响。应用中也有几处使用了 useMemouseCallback,我很想看看借助编译器是否能去掉这些手动优化。

健康检查

有一个 react-compiler-healthcheck 包,可以用来检查你的代码库是否与编译器兼容。

我通过 npx react-compiler-healthcheck 运行了检查,结果令人鼓舞:

Successfully compiled 43 out of 45 components.
StrictMode usage found.
Found no usage of incompatible libraries.

ESLint 插件

还有一个 ESLint 插件可以独立于编译器使用。

我把 eslint-plugin-react-compiler 加到了 ESLint 配置中,然后运行了检查。

但这次就不那么顺利了——ESLint 抛出了一个错误:

TypeError: Error while loading rule 'react-compiler/react-compiler': Cannot read properties of undefined (reading 'endsWith')
Occurred while linting /home/aocallaghan/dev/frontend-app-shell/.eslintrc.js

我尝试指定不同的文件(比如只检查 React 组件),但它在遇到的第一个文件上就报错了。出问题的是这一行:

if (context.filename.endsWith(".tsx") || context.filename.endsWith(".ts")) {

context.filename 未定义,而 filename 变量在上层作用域中已经定义。我在本地做了修改,改用 filename 变量,之后就能正常运行且没有报错了。

后来发现这是一个 bug,现已修复:github.com/facebook/react/pull/29104

与 Babel 集成

我们使用 Webpack + Babel 来打包应用。添加 babel-plugin-react-compiler 看起来是最简单的方案。我更新了 babel.config.js,把该插件作为首个插件引入(按照 文档中的说明)。

到这里,我在解析 react/compiler-runtime 时遇到了错误——这也在意料之中,因为我们需要使用 React 19 的 Beta 版本。

我升级到了 React 19

npm install react@beta react-dom@beta

Webpack 构建成功了。但应用却是一片空白,控制台里出现了错误:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'ReactCurrentDispatcher')

在 Google 上快速搜索了一下,发现这是 reactreact-dom 版本不一致时的常见错误。我尝试使用 Yarn 的 resolutions 配置来强制所有依赖都使用 React 19,但在重新生成 yarn.lock 并重装 node_modules 后,依然存在运行时错误。

追踪错误后发现,问题出在我们用于 OAuth2 认证的 react-oidc 包上。该包在库内部包含了 react-jsx-runtime,这正是导致版本不匹配的根源。我在本地修改了 vite.config.ts 文件,并重新在本地构建了该包。

我已经在上游提交了 issue 来推动修复(github.com/AxaFrance/oidc-client/issues/1370)。

接着又出现了另一个错误:

Uncaught (in promise) TypeError: (0 , react__WEBPACK_IMPORTED_MODULE_0__.createFactory) is not a function

createFactory 已在 React 19 中被移除。这个错误来自 recompose 包,而它是被我们内部的一些包所引入的。自 React Hooks 推出以来,recompose 就已被废弃,只是我们一直没有处理掉这个依赖。

我查看了仍在使用 recompose 的两个包:

  • 一个虽然声明了该依赖,但实际上已不再使用,我直接移除了这个无用的依赖
  • 另一个主要用来设置 defaultProps——为了测试应用,我并不需要这些组件真正可用,于是去掉了相关代码和依赖,并记下后续需要正确修复

接下来是代码库本身的最后一个错误——我们还在调用 ReactDOM.render,而它现在已被 createRoot 取代。很快就修复了。

大功告成!应用能够正常渲染和运行了!

性能评估

我使用 React Developer Tools Profiler 做了接入前后的快速对比。

  • 接入前,初次渲染我们其中一个应用时会产生 20 次提交,接入 React Compiler 后降至 16 次提交
  • 我移除了应用中所有的 useCallbackuseMemo 用法,初次加载时的提交次数也仅为 17 次

开发者工具还会高亮显示编译器已为你自动记忆化组件渲染的位置:

React Developer Tools 显示 React Compiler 的优化效果

本文章由 muse-spark-1.2-contributor 进行翻译

评论