自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

苦等三年,React Compiler 終于能用了。使用體驗:很爽,但仍有瑕疵

開發(fā) 前端
要了解 React Compiler,這還需要從 React 的更新機制說起。React 項目中的任何一個組件發(fā)生 state 狀態(tài)的變更,React 更新機制都會從最頂層的根節(jié)點開始往下遞歸對比,通過雙緩存機制判斷出哪些節(jié)點發(fā)生了變化,然后更新節(jié)點。

一、React Compiler

React Compiler 終于開源了。

自從從它第一次在 React Conf 2021 亮相。到現(xiàn)在 React Conf 2024 正式開源,我已經(jīng)苦等了三年之久。盼星星盼月亮,終于把他給盼來了。

i

以前叫 React Forget,現(xiàn)改名為 React Compiler。

要了解 React Compiler,這還需要從 React 的更新機制說起。React 項目中的任何一個組件發(fā)生 state 狀態(tài)的變更,React 更新機制都會從最頂層的根節(jié)點開始往下遞歸對比,通過雙緩存機制判斷出哪些節(jié)點發(fā)生了變化,然后更新節(jié)點。這樣的更新機制成本并不小,因為在判斷過程中,如果 React 發(fā)現(xiàn) props、state、context 任意一個不同,那么就認(rèn)為該節(jié)點被更新了。因此,冗余的 re-render 在這個過程中會大量發(fā)生。

?

對比的成本非常小,但是 re-render 的成本偏高,當(dāng)我們在短時間之內(nèi)快速更改 state 時,程序大概率會存在性能問題。因此在以往的開發(fā)方式中,掌握性能優(yōu)化的手段是高級 React 開發(fā)者的必備能力。

一個組件節(jié)點在 React 中很難被判斷為沒有發(fā)生過更新。因為 props 的比較總是不同的。它的比較方式如下。

{} === {} // false

因此,高級 React 開發(fā)者需要非常了解 React 的默認(rèn)優(yōu)化機制,讓 props 的比較不發(fā)生,因為一旦發(fā)生,那么結(jié)果必定是 false。

i

事實上,對 React 默認(rèn)優(yōu)化機制了解的開發(fā)者非常少,我們在開發(fā)過程中也不會為了優(yōu)化這個性能去重新調(diào)整組件的分布。更多的還是使用 memo 與 useMemo/useCallback 暴力緩存節(jié)點。

在這樣的背景之下,冗余的 re-render 在大量的項目中發(fā)生。這也是為什么 React 總是唄吐槽性能不好的主要原因。當(dāng)然,大多數(shù)項目并沒有頻繁更新 state 的需求,因此這一點性能問題表現(xiàn)得并不是很明顯。

如果我們要解決冗余 re-render 的問題,需要對 React 默認(rèn)優(yōu)化技能有非常深刻的理解,需要對 memo、useCallback、useMemo 有準(zhǔn)確的理解。但是普通的 React 開發(fā)者很難理解他們,有的開發(fā)者雖然在項目中大量使用了,但是未必就達到了理想的效果。React Compiler 則是為了解決這個問題,它可以自動幫助我們記憶已經(jīng)存在、并且沒有發(fā)生更新的組件,從而解決組件冗余 re-render 的問題。

從使用結(jié)果的體驗來看,React Compiler 被集成在代碼自動編譯中,因此只要我們在項目中引入成功,就不再需要關(guān)注它的存在。我們的開發(fā)方式不會發(fā)生任何改變。它不會更改 React 現(xiàn)有的開發(fā)范式和更新方式,侵入性非常弱。

二、檢測

并非所有的組件都能被優(yōu)化。因此早在 React 18 的版本中,React 官方團隊就提前發(fā)布了嚴(yán)格模式。在頂層根節(jié)點中,套一層 StrictMode 即可。

<StrictMode>
  <BrowserRouter>
    <App />
  </BrowserRouter>
</StrictMode>

遵循嚴(yán)格模式的規(guī)范,我們的組件更容易符合 React Compiler 的優(yōu)化規(guī)則。

我們可以使用如下方式首先檢測代碼庫是否兼容。在項目根目錄下執(zhí)行如下指令。

npx react-compiler-healthcheck

?

該腳本主要用于檢測。

1、項目中有多少組件可以成功優(yōu)化:越多越好。

2、是否使用嚴(yán)格模式,使用了優(yōu)化成功率更高。

3、是否使用了與 Compiler 不兼容的三方庫。

例如,我的其中一個項目,檢測結(jié)果如下:

每一項都基本上通過了,那我就可以放心的在項目中引入對應(yīng)的插件開始體驗了。

三、在項目中引入

官方文檔中已經(jīng)明確表示,由于 JavaScript 的靈活性,Compiler 無法捕獲所有可能的意外行為,甚至編譯之后還會出現(xiàn)錯誤。因此,目前而言,Compiler 依然可能會有他粗糙的一面。因此,我們可以通過配置,在具體的某一個小目錄中運行 Compiler。

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

React Compiler 還支持對應(yīng)的 eslint 插件。該插件可以獨立運行。不用非得與 Compiler 一起運行。

可以使用如下指令安裝該插件。

npm i eslint-plugin-react-compiler

然后在 eslint 的配置中添加。

module.exports = {
  plugins: [
    'eslint-plugin-react-compiler',
  ],
  rules: {
    'react-compiler/react-compiler': 2,
  },
}

Compiler 目前結(jié)合 Babel 插件一起使用,因此,我們首先需要在項目中引入該插件。

npm i babel-plugin-react-compiler

然后,在不同的項目中,有不同的配置。

添加到 Babel 的配置中,如下所示

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

i

注意,該插件應(yīng)該在其他 Babel 插件之前運行。

在 vite 中使用

首先,我們需要安裝 vite-plugin-react,注意不用搞錯了,群里有的同學(xué)使用了 vite-plugin-react-swc 結(jié)果搞了很久沒配置成功。然后在 vite.config.js 中,添加如下配置。

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

在 Next.js 中使用

創(chuàng)建 babel.config.js 并添加上面 Babel 同樣的配置即可。

在 Remix 中使用

安裝如下插件,并且添加對應(yīng)的配置項目。

npm i 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 中使用

我們可以單獨為 Compiler 創(chuàng)建一個 Loader. 代碼如下所示。

const ReactCompilerConfig = { /* ... */ };
const BabelPluginReactCompiler = require('babel-plugin-react-compiler');

function reactCompilerLoader(sourceCode, sourceMap) {
  // ...
  const result = transformSync(sourceCode, {
    // ...
    plugins: [
      [BabelPluginReactCompiler, ReactCompilerConfig],
    ],
  // ...
  });

  if (result === null) {
    this.callback(
      Error(
        `Failed to transform "${options.filename}"`
      )
    );
    return;
  }

  this.callback(
    null,
    result.code
    result.map === null ? undefined : result.map
  );
}

module.exports = reactCompilerLoader;

我們可以在 React 官方了解到更多關(guān)于 React Compiler 的介紹與注意事項。具體地址如下:https://react.dev/learn/react-compiler

我目前已經(jīng)在 vite 項目中引入,并將項目成功啟動。接下來,就談?wù)勎业氖褂皿w驗。

四、真實項目的使用體驗

當(dāng)項目成功啟動,之后,我們可以在 React Devtools v5.x 的版本中,看到被優(yōu)化過的組件旁邊都有一個 Memo 標(biāo)識。如圖所示。

如果我們要運行 React Devtools,安裝成功之后,需要將如下代碼加入到 html 文件中。這樣我們就可以利用它來調(diào)試 React 項目了。

<script src="http://localhost:8097"></script>

如果是已有的老項目,我們最好刪除 node_modules 并重新安裝以來。不然項目運行起來可能會報各種奇怪的錯誤。如果還是不行,可以把 React 版本升級到 19 試試。

總之折騰了一會兒,我成功運行了一個項目。我目前就寫了一個簡單的組件來測試他的優(yōu)化效果。代碼如下:

function Index() {
  const [counter, setCounter] = useState(0)

  function p() {
    console.log('函數(shù)執(zhí)行 ')
  }

  return (
    <div>
      <button onClick={() => setCounter(counter + 1)}>
        點擊修改 counter:{counter}
      </button>
      <Children a={1} b={2} c={p} />
    </div>
  )
}

我們先來分析一下這段代碼。首先,在父組件中,我們設(shè)計了一個數(shù)字遞增的狀態(tài)。當(dāng)點擊發(fā)生時,狀態(tài)遞增。此時父組件會重新 render。因此,在以往的邏輯中,子組件 Children 由于沒有使用任何優(yōu)化手段,因此,在父組件重新渲染時,子組件由于 props 的比較結(jié)果為 false,也會重新執(zhí)行。

并且其中一個 props 屬性還是一個引用對象,因此我們需要使用 useCallback + memo 才能確保子組件 Children 不會冗余 re-render。

但是此時,我們的組件已經(jīng)被 React Compiler 優(yōu)化過,因此,理論上來說,冗余 re-render 的事情應(yīng)該不會發(fā)生,嘗試了一下,確實如此。如圖,我點擊了很多次按鈕,counter 遞增,但是 Children 并沒有冗余執(zhí)行。

很棒。

這里需要注意的是,引入了 Compiler 插件之后,它會自動工作,我們完全不用關(guān)注它的存在。因此,如果程序不出問題,對于開發(fā)者來說,編譯工作是無感的。所以開發(fā)體驗非常棒。

!

不過有一些美中不足的是,當(dāng)我嘗試驗證其他已經(jīng)寫好的組件被編譯之后是否存在問題時,發(fā)現(xiàn)有一個組件的運行邏輯發(fā)生了變化。目前我還沒有深究具體是什么原因?qū)е碌?,不過通過對比,這個組件的獨特之處在與,我在該組件中使用了 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>
    </>
  )
}

理想情況是這種情況可以不用發(fā)生 re-render。因此總體來說,Compiler 目前確實還不能完全信任。也有可能我還沒掌握正確的姿勢,還需要對他有更進一步的了解才可以。

不過值得高興的是,新項目可以放心使用 Compiler,因為運行結(jié)果我們都能實時感知、調(diào)試、調(diào)整,能最大程度的避免問題的出現(xiàn)。

五、原理

React Compiler 編譯之后的代碼并非是在合適的時機使用 useMemo/memo 等 API 來緩存組件。而是使用了一個名為 useMemoCache 的 hook 來緩存代碼片段。

Compiler 會分析所有可能存在的返回結(jié)果,并把每個返回結(jié)果都存儲在 useMemoCache 中。如上圖所示,他打破了原有的鏈表存儲結(jié)果,而選擇把緩存結(jié)構(gòu)存儲在數(shù)組上。因此在執(zhí)行效率上,Compiler 之后的代碼會高不少。每一個渲染結(jié)果都會被存儲在 useMemoCache 的某一項中,如果判斷之后發(fā)現(xiàn)該結(jié)果可以復(fù)用,則直接通過讀取序列的方式使用即可。如圖所示。

因此,編譯之后的代碼看上去會更加的繁雜。但是執(zhí)行卻會更加高效。

六、總結(jié)

初次感受下來,雖然感覺還不錯。但是依然會有一種自己寫的代碼被魔改的不適感。特別是遇到問題的時候,還不知道到底編譯器干了什么事情讓最終運行結(jié)果與預(yù)想的完全不同。

i

這也是我不太喜歡使用 Solid 與 Svelte 的根本原因。不過 React 好在可以不用...

但是從執(zhí)行性能上確實會有大的提高,這一點對于初學(xué)者可能會比較友好。

目前,由于接觸時間太短,我對于 React Compiler 的使用體驗還停留在比較淺的層面。因此能聊的東西并不多,在后續(xù)我有了更進一步更深刻的體會之后,再來跟大家分享體驗結(jié)果。

責(zé)任編輯:姜華 來源: 這波能反殺
相關(guān)推薦

2021-04-17 07:31:54

阿里云盤PC 版應(yīng)用

2021-07-05 12:30:17

ChromebookSkylake處理器Chrome OS

2012-07-31 09:19:02

程序員

2024-12-09 08:21:49

2010-02-26 10:22:11

IPv4

2016-01-08 10:32:48

虛擬現(xiàn)實頭盔

2021-07-08 07:52:48

微服務(wù)業(yè)務(wù)架構(gòu)

2012-05-04 15:49:14

BYOD

2013-06-25 14:11:19

html5Java 7

2020-06-03 16:03:49

iOS 14蘋果通話錄音

2019-10-30 15:35:47

Android谷歌手機

2020-10-13 18:10:46

Kubernetes容器化云計算

2021-09-13 22:25:52

手機缺陷技術(shù)

2018-07-23 13:15:12

CDN網(wǎng)宿阿里云

2018-05-06 23:30:11

Windows Mobile 系統(tǒng)

2021-06-16 06:14:29

React17Toast組件React bug

2013-07-17 09:13:19

2022-02-16 09:55:39

Elastic 8開源索引

2023-04-23 13:30:19

ERP廠商華為

2023-03-03 07:40:52

MySQLSQL命令
點贊
收藏

51CTO技術(shù)棧公眾號