likes
comments
collection
share

React 代码再也不用写 memo 了,这也太神奇了吧!

作者站长头像
站长
· 阅读数 55

前言

在 React Conf 2024,React Compiler 终于开源了,第一次亮相是在 React Conf 2021,那时还叫做 React Forget,是黄玄做的一次 sharing - React without memo

在黄玄加入字节之后,Ask me anything 内部分享,大家最关心的除了恋综后续,还有就是 React Forgot 的后续,那时黄玄提到,实际上在 Meta 内部,React Forget 实际上已经在 Instagram 等业务完成落地,另外 React 19 在 Meta 内部早已落地,并且在快速推进。在 Meta 公司与国内大厂有一些区别,在 Meta 内部全公司只有一个 monorepo,所以在推进 React 升级上比较好做,只需要由基础架构 Infra 团队完成版本升级即可。

只是对于 Meta 公司内部来说,内部使用价值大于开源价值,所以迟迟没有发布 React 19 正式版本。

React 代码再也不用写 memo 了,这也太神奇了吧!

Compiler 是什么

React 的更新机制是,任何一个组件发生 state 状态的变更,React 都会从最顶层的根节点开始往下递归对比,通过双缓存机制判断出哪些节点发生了变化,然后更新节点。Re-render 的现象是很普遍的,不过 React 也提供了方式进行手动 Memo 优化。

简单的说,Compiler 是一个 babel 插件,它在编译阶段可以自动对 React 代码词法进行分析,添加 useMemo、useCallback、memo 等缓存函数,降低开发者的心智负担。

React 官方也认为让用户手动 memo 只是一种折衷,希望 React 能在状态发生变化时能够自动渲染,所以愿意为 React 构建优化方向编译器投入精力。在 Complier 出现之前,React 并没有一个 tokenize 的概念,Complier 实际上赋予了 React 编程语言的能力,React Team 基于 React Complier 实现了 memo 的自动优化。

Savona:React Compiler 更像 TypeScript,或者 JavaScript 引擎中的编译器,比如 V8 或 Hermes,”他说到。“它将我们的代码分解成单独的表达式,并构建一个控制流和数据流图。它执行复杂的优化,如死代码消除、常量传播、类型推断,甚至别名分析等等。这些优化通常会出现在 JavaScript 引擎或 Rust 或 C++ 等语言的编译器中。

React 代码再也不用写 memo 了,这也太神奇了吧!

如何使用

健康检查

首先可以执行 healthcheck 来检查能够优化多少个组件

npx react-compiler-healthcheck@latest

这个命令执行之后会显示现在有多少个组件在引入 React Compiler 之后可以被自动优化,以及是否启用 <StrictMode> 模式

扫描之后结果如下:

Successfully compiled 8 out of 9 components.
StrictMode usage not found.
Found no usage of incompatible libraries.

ESlint 插件接入

npm install eslint-plugin-react-compiler

也提供了 ESlint 插件进行优化检测,可以独立使用,不需要引入 React Compiler 编译器

babel 插件

前提需要满足 React 版本 >=19,安装 babel-plugin-react-compiler 插件

npm install babel-plugin-react-compiler
"dependencies": {
  "babel-plugin-react-compiler": "0.0.0-experimental-487cb0e-20240529",
  "react": "19.0.0-rc-6f23540c7d-20240528",
  "react-dom": "19.0.0-rc-6f23540c7d-20240528",
},

引入 babel 插件

为了降低 React Compiler 对项目的影响,插件提供了最小目录的接入方式,可以只对声明目录的文件生效。

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

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

vite 接入可以参考如下代码

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['babel-plugin-react-compiler'],
      },
    }),
  ],
});

根据官方的描述,当前 React Complier 需要运行在 React 19 RC 版本上,但是针对低版本,React 官网也提供了 polyfill 的方式,但不是很推荐使用该方式接入,因为该 Polyfill 的行为和 React 的内置实现略有不同,在相同情况下可能不总是保留缓存的值。

使用效果

让我们写一个简单的组件

import { useState } from 'react';
import './App.css';

function App() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount((count) => count + 1)}>
      count is {count}
    </button>
  );
}

export default App;

当 React 代码被 React Compiler 编译过之后,会变成下方的样子,自动引入一段 memo_cache 逻辑。

尝试本地使用 Compiler 进行打包,输出的代码结构与 React Compiler Playground 是一致的。

简单来说最终的效果是,Compiler 在编译之后,自动帮我们组件添加了 useMemouseCallbackmemo 等逻辑,源码逻辑在 $ 函数内

React 代码再也不用写 memo 了,这也太神奇了吧!

React 代码再也不用写 memo 了,这也太神奇了吧!

你可能会好奇生成函数的 $c 函数是做什么的。

生成的代码中的 $ 是缓存值及其依赖项的数组,它通过调用内部 React 函数(此处命名 c)创建的。缓存函数不是公共 API,只是用于 React 编译器的函数。

可以在 React devtools 5+ 版本查看优化效果,可以看到 devtools 提示,该组件已经被 memorized 了

React 代码再也不用写 memo 了,这也太神奇了吧!

Meta 落地效果

目前在 Meta instagram 和 Quest 商店等大型 App 已完成落地,编译器优化版本的速度是以前的两倍多。初始加载时间和跨页面导航时间提高了 12%。而且对内存使用率或整体崩溃情况都没有影响,导航任务加载速度至少提高了 4%,Instagram 平均提高了 3%。

对于 Ins 这种类型的 app 来说,在一个特定的页面上,即便性能指标上提高 1-2 %,也是一个很不错的优化。

原理

React Compiler 底层使用 Rust 编写,源码就有几十万行,非常复杂。

查看 Compiler Rust 部分源码,可以看到底层使用 napi-rs 工具的能力,可以把 Rust 代码编译成 Node-addons,在 JS 中执行。

React 代码再也不用写 memo 了,这也太神奇了吧!

  • 在 Compiler 仓库中,README 部分介绍了编译器的设计原则,可见链接

如何贡献

  • React 官方有一个 Working group 正在做相关的工作,如果说感兴趣的话,可以加入 Group一起讨论共建 Compiler
  • Compiler 在 github 有一个讨论区,工作组会同步 Compiler 相关介绍以及其它内容,链接

总结

React Compiler 技术很有意义,在于他是对使用者无感的,即便对于开发者也不是破坏性改动,虽然原理极其复杂,但作为开发者我们只需要关注如何引入消费即可。

对于 Webpack / Vite 接入的项目,可以在 React 19 正式版上线之后开始接入,但是 Complier 的稳定性还需要社区进一步观察,目前还属于实验阶段,React 官方提供了 Playground 供开发者提供问题复现路径,供官方进行编译器优化。对业务来说,我的看法是先观察一段时间,如果项目历史包袱比较轻,未来能升到 19 版本,可以尝试接入。

转载自:https://juejin.cn/post/7372523264067043337
评论
请登录