React Compiler 终于开源了。
自从从它第一次在 React Conf 2021 亮相。到现在 React Conf 2024 正式开源,我已经苦等了三年之久。盼星星盼月亮,终于把他给盼来了。
i
以前叫 React Forget,现改名为 React Compiler。
要了解 React Compiler,这还需要从 React 的更新机制说起。React 项目中的任何一个组件发生 state 状态的变更,React 更新机制都会从最顶层的根节点开始往下递归对比,通过双缓存机制判断出哪些节点发生了变化,然后更新节点。这样的更新机制成本并不小,因为在判断过程中,如果 React 发现 props、state、context 任意一个不同,那么就认为该节点被更新了。因此,冗余的 re-render 在这个过程中会大量发生。
✓
对比的成本非常小,但是 re-render 的成本偏高,当我们在短时间之内快速更改 state 时,程序大概率会存在性能问题。因此在以往的开发方式中,掌握性能优化的手段是高级 React 开发者的必备能力。
一个组件节点在 React 中很难被判断为没有发生过更新。因为 props 的比较总是不同的。它的比较方式如下。
{} === {} // false
因此,高级 React 开发者需要非常了解 React 的默认优化机制,让 props 的比较不发生,因为一旦发生,那么结果必定是 false。
i
事实上,对 React 默认优化机制了解的开发者非常少,我们在开发过程中也不会为了优化这个性能去重新调整组件的分布。更多的还是使用 memo 与 useMemo/useCallback 暴力缓存节点。
在这样的背景之下,冗余的 re-render 在大量的项目中发生。这也是为什么 React 总是呗吐槽性能不好的主要原因。当然,大多数项目并没有频繁更新 state 的需求,因此这一点性能问题表现得并不是很明显。
如果我们要解决冗余 re-render 的问题,需要对 React 默认优化技能有非常深刻的理解,需要对 memo、useCallback、useMemo 有准确的理解。但是普通的 React 开发者很难理解他们,有的开发者虽然在项目中大量使用了,但是未必就达到了理想的效果。React Compiler 则是为了解决这个问题,它可以自动帮助我们记忆已经存在、并且没有发生更新的组件,从而解决组件冗余 re-render 的问题。
从使用结果的体验来看,React Compiler 被集成在代码自动编译中,因此只要我们在项目中引入成功,就不再需要关注它的存在。我们的开发方式不会发生任何改变。它不会更改 React 现有的开发范式和更新方式,侵入性非常弱。
并非所有的组件都能被优化。因此早在 React 18 的版本中,React 官方团队就提前发布了严格模式。在顶层根节点中,套一层 StrictMode 即可。
<StrictMode> <BrowserRouter> <App /> </BrowserRouter></StrictMode>
遵循严格模式的规范,我们的组件更容易符合 React Compiler 的优化规则。
我们可以使用如下方式首先检测代码库是否兼容。在项目根目录下执行如下指令。
npx react-compiler-healthcheck
✓
该脚本主要用于检测。
1、项目中有多少组件可以成功优化:越多越好。
2、是否使用严格模式,使用了优化成功率更高。
3、是否使用了与 Compiler 不兼容的三方库。
例如,我的其中一个项目,检测结果如下:
很棒。
这里需要注意的是,引入了 Compiler 插件之后,它会自动工作,我们完全不用关注它的存在。因此,如果程序不出问题,对于开发者来说,编译工作是无感的。所以开发体验非常棒。
!
不过有一些美中不足的是,当我尝试验证其他已经写好的组件被编译之后是否存在问题时,发现有一个组件的运行逻辑发生了变化。目前我还没有深究具体是什么原因导致的,不过通过对比,这个组件的独特之处在与,我在该组件中使用了 useDeferredValue 来处理异步请求。
另外,Compiler 也不能阻止 context 组件的 re-render。例如我在一个组件中使用了 use(context) ,哪怕我并没有使用具体的值。如下所示。
import {use} from 'react'import {Context} from './context'export default function Card() { const value = use(Context) console.log('xxxxx context') return ( <> <div className='_06_card'> <div className="title">Canary</div> <p>The test page</p> </div> </> )}
理想情况是这种情况可以不用发生 re-render。因此总体来说,Compiler 目前确实还不能完全信任。也有可能我还没掌握正确的姿势,还需要对他有更进一步的了解才可以。
不过值得高兴的是,新项目可以放心使用 Compiler,因为运行结果我们都能实时感知、调试、调整,能最大程度的避免问题的出现。
React Compiler 编译之后的代码并非是在合适的时机使用 useMemo/memo 等 API 来缓存组件。而是使用了一个名为 useMemoCache 的 hook 来缓存代码片段。
Compiler 会分析所有可能存在的返回结果,并把每个返回结果都存储在 useMemoCache 中。如上图所示,他打破了原有的链表存储结果,而选择把缓存结构存储在数组上。因此在执行效率上,Compiler 之后的代码会高不少。每一个渲染结果都会被存储在 useMemoCache 的某一项中,如果判断之后发现该结果可以复用,则直接通过读取序列的方式使用即可。如图所示。
因此,编译之后的代码看上去会更加的繁杂。但是执行却会更加高效。
初次感受下来,虽然感觉还不错。但是依然会有一种自己写的代码被魔改的不适感。特别是遇到问题的时候,还不知道到底编译器干了什么事情让最终运行结果与预想的完全不同。
i
这也是我不太喜欢使用 Solid 与 Svelte 的根本原因。不过 React 好在可以不用...
但是从执行性能上确实会有大的提高,这一点对于初学者可能会比较友好。
目前,由于接触时间太短,我对于 React Compiler 的使用体验还停留在比较浅的层面。因此能聊的东西并不多,在后续我有了更进一步更深刻的体会之后,再来跟大家分享体验结果。
本文链接:http://www.28at.com/showinfo-26-94294-0.html苦等三年,React Compiler 终于能用了。使用体验:很爽,但仍有瑕疵
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com