React 編譯器

此頁面將介紹 React 編譯器以及如何成功試用它。

建構中

這些文件仍在開發中。更多文件可在 React 編譯器工作組 repo 中找到,並將在更穩定時上傳到這些文件中。

你將學習

  • 編譯器入門
  • 安裝編譯器和 ESLint 插件
  • 疑難排解

注意事項

React 編譯器是一個目前處於 Beta 階段的新編譯器,我們已將其開源以從社群獲得早期反饋。雖然它已在 Meta 等公司的生產環境中使用,但將編譯器應用於你的應用程式的生產環境將取決於你的程式碼庫的健康狀況以及你遵循 React 規則 的程度。

最新的 Beta 版本可以使用 @beta 標籤找到,每日實驗版本可以使用 @experimental 標籤找到。

React 編譯器是一個我們已開源的新編譯器,旨在從社群獲得早期反饋。它是一個僅在建構時運作的工具,可自動優化你的 React 應用程式。它適用於純 JavaScript,並理解 React 規則,因此你無需重寫任何程式碼即可使用它。

該編譯器還包含一個 ESLint 插件,可在你的編輯器中顯示編譯器的分析結果。我們強烈建議每個人都使用這個程式碼檢查工具。 該程式碼檢查工具不需要你安裝編譯器,因此即使你尚未準備好試用編譯器,也可以使用它。

該編譯器目前以 beta 版本發布,可在 React 17+ 應用程式和程式庫中試用。要安裝 Beta 版本

終端機
npm install -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta

或者,如果你使用的是 Yarn

終端機
yarn add -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta

如果你尚未使用 React 19,請參閱以下章節以獲取進一步說明。

編譯器有什麼作用?

為了優化應用程式,React 編譯器會自動記憶你的程式碼。你可能已經透過 useMemouseCallbackReact.memo 等 API 熟悉記憶。使用這些 API,你可以告訴 React,如果應用程式的某些部分的輸入沒有更改,則無需重新計算,從而減少更新的工作量。雖然功能強大,但很容易忘記應用記憶或錯誤地應用它們。這可能導致效率低下的更新,因為 React 必須檢查 UI 中沒有任何*有意義*更改的部分。

編譯器利用其對 JavaScript 和 React 規則的了解,自動記憶元件和 hooks 中的值或值組。如果它檢測到規則被破壞,它將自動跳過這些元件或 hooks,並繼續安全地編譯其他程式碼。

注意事項

React 編譯器可以靜態檢測 React 規則何時被破壞,並安全地選擇不優化受影響的元件或 hooks。編譯器不需要優化 100% 的程式碼庫。

如果你的程式碼庫已經很好地記憶化,你可能不會期望編譯器帶來重大的效能提升。然而,在實務中,手動正確地記憶導致效能問題的依賴關係是很棘手的。

深入探討

React 編譯器新增了哪種記憶?

React Compiler 的初始版本主要著重於提升更新效能(重新渲染現有元件),因此它聚焦於以下兩個用例

  1. 跳過元件的級聯重新渲染
    • 重新渲染 <Parent /> 會導致其元件樹中的許多元件重新渲染,即使只有 <Parent /> 發生了變化
  2. 跳過 React 以外的耗時計算
    • 例如,在您的元件或需要該資料的 hook 中呼叫 expensivelyProcessAReallyLargeArrayOfObjects()

最佳化重新渲染

React 允許您將 UI 表示為其當前狀態的函數(更具體地說:它們的 props、state 和 context)。在其目前的實作中,當元件的狀態發生變化時,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 判斷即使 friends 發生變化,<FriendListCard /> 的返回值也可以重複使用,並且可以避免重新建立此 JSX *並*避免在計數更改時重新渲染 <MessageButton>

耗時計算也會被記憶體化

編譯器還可以自動記憶體化渲染過程中使用的耗時計算

// **Not** memoized by React Compiler, since this is not a component or hook
function expensivelyProcessAReallyLargeArrayOfObjects() { /* ... */ }

// Memoized by React Compiler since this is a component
function TableContainer({ items }) {
// This function call would be memoized:
const data = expensivelyProcessAReallyLargeArrayOfObjects(items);
// ...
}

在 React Compiler Playground 中查看此範例

然而,如果 expensivelyProcessAReallyLargeArrayOfObjects 真的是一個耗時函數,您可能需要考慮在 React 之外實作它自己的記憶體化,因為

  • React Compiler 只會記憶體化 React 元件和 hooks,而不是每個函數
  • React Compiler 的記憶體化不會在多個元件或 hooks 之間共享

因此,如果在許多不同的元件中使用了 expensivelyProcessAReallyLargeArrayOfObjects,即使傳遞了相同的項目,該耗時計算也會重複執行。我們建議您先進行效能分析,確認它是否真的那麼耗時,再讓程式碼變得更複雜。

我應該試用編譯器嗎?

請注意,編譯器仍處於 Beta 階段,還有許多粗糙之處。雖然它已在 Meta 等公司的生產環境中使用,但將編譯器部署到您的應用程式生產環境取決於您的程式碼庫的健康狀況以及您遵循 React 規則 的程度。

您不必急於現在就使用編譯器。可以等到它達到穩定版本后再採用它。 然而,我們非常感謝您在應用程式中進行小型實驗來試用它,以便您可以 提供反饋 給我們,幫助我們改进編譯器。

開始使用

除了這些文件之外,我們還建議您查看 React Compiler 工作組 以取得關於編譯器的更多資訊和討論。

安裝 eslint-plugin-react-compiler

React Compiler 也支援 ESLint 插件。ESLint 插件可以獨立於編譯器使用,這表示即使您不使用編譯器,也可以使用 ESLint 插件。

終端機
npm install -D eslint-plugin-react-compiler@beta

然後,將其添加到您的 ESLint 設定中

import reactCompiler from 'eslint-plugin-react-compiler'

export default [
{
plugins: {
'react-compiler': reactCompiler,
},
rules: {
'react-compiler/react-compiler': 'error',
},
},
]

或者,以已棄用的 eslintrc 設定格式

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

ESLint 插件會在您的編輯器中顯示任何違反 React 規則的情況。發生這種情況時,表示編譯器已跳過最佳化該元件或 hook。這完全沒問題,編譯器可以恢復並繼續最佳化程式碼庫中的其他元件。

注意事項

您不必立即修復所有 ESLint 違規。您可以按照自己的步調解決它們,以增加被最佳化的元件和 hooks 的數量,但在使用編譯器之前並不需要修復所有問題。

將編譯器部署到您的程式碼庫

現有專案

此編譯器旨在編譯遵循 React 規則 的函式組件和 Hooks。它也可以處理違反這些規則的程式碼,方法是跳過這些組件或 Hooks。然而,由於 JavaScript 的靈活性,編譯器無法捕捉所有可能的違規行為,並可能編譯出偽陰性結果:也就是說,編譯器可能會意外編譯違反 React 規則的組件/Hooks,從而導致未定義的行為。

因此,為了在現有專案中成功導入編譯器,我們建議先在產品程式碼中的一個小目錄上運行它。您可以通過將編譯器配置為僅在特定目錄集上運行來做到這一點。

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

當您對編譯器的推出更有信心時,您可以將覆蓋範圍擴展到其他目錄,並逐步將其推廣到整個應用程式。

新專案

如果您正在開始一個新專案,則可以在整個程式碼庫上啟用編譯器,這是預設行為。

將 React Compiler 與 React 17 或 18 搭配使用

React Compiler 與 React 19 RC 搭配使用效果最佳。如果您無法升級,可以安裝額外的 react-compiler-runtime 套件,這將允許編譯後的程式碼在 19 之前的版本上運行。但是,請注意,最低支援版本為 17。

終端機
npm install react-compiler-runtime@beta

您還應該在編譯器配置中添加正確的 target,其中 target 是您目標 React 的主要版本。

// babel.config.js
const ReactCompilerConfig = {
target: '18' // '17' | '18' | '19'
};

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

在函式庫上使用編譯器

React Compiler 也可用於編譯函式庫。由於 React Compiler 需要在任何程式碼轉換之前在原始程式碼上運行,因此應用程式的建置流程無法編譯它們使用的函式庫。因此,我們建議函式庫維護者使用編譯器獨立編譯和測試他們的函式庫,並將編譯後的程式碼發佈到 npm。

由於您的程式碼已預先編譯,因此您的函式庫的使用者無需啟用編譯器即可受益於應用於您函式庫的自動記憶體優化。如果您的函式庫目標應用程式尚未使用 React 19,請指定最低 target 並將 react-compiler-runtime 作為直接依賴項添加。運行時套件將根據應用程式的版本使用正確的 API 實作,並在必要時填充缺少的 API。

函式庫程式碼通常需要更複雜的模式和跳脫方法的使用。因此,我們建議確保您有足夠的測試,以便識別在您的函式庫上使用編譯器可能引起的任何問題。如果您發現任何問題,您可以隨時使用 'use no memo' 指令 選擇退出特定的組件或 Hooks。

與應用程式類似,沒有必要完全編譯 100% 的組件或 Hooks 才能在您的函式庫中看到好處。一個好的起點可能是識別函式庫中最注重效能的部分,並確保它們沒有違反 React 規則,您可以使用 eslint-plugin-react-compiler 來識別。

用法

Babel

終端機
npm install babel-plugin-react-compiler@beta

編譯器包含一個 Babel 插件,您可以在構建流程中使用它來運行編譯器。

安裝後,將其添加到您的 Babel 配置中。請注意,編譯器在流程中**首先**運行至關重要。

// babel.config.js
const ReactCompilerConfig = { /* ... */ };

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

babel-plugin-react-compiler 應該在其他 Babel 插件之前運行,因為編譯器需要輸入的程式碼資訊才能進行完善的分析。

Vite

如果您使用 Vite,可以將插件添加到 vite-plugin-react 中。

// vite.config.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: [
remix({ /* ... */}),
babel({
filter: /\.[jt]sx?$/,
babelConfig: {
presets: ["@babel/preset-typescript"], // if you use TypeScript
plugins: [
["babel-plugin-react-compiler", ReactCompilerConfig],
],
},
}),
],
});

Webpack

社群 Webpack loader 現已在此處提供。

Expo

請參考Expo 的文件,在 Expo 應用程式中啟用和使用 React Compiler。

Metro (React Native)

React Native 透過 Metro 使用 Babel,因此請參閱「與 Babel 搭配使用」一節以取得安裝說明。

Rspack

請參考Rspack 的文件,在 Rspack 應用程式中啟用和使用 React Compiler。

Rsbuild

請參考Rsbuild 的文件,在 Rsbuild 應用程式中啟用和使用 React Compiler。

疑難排解

要回報問題,請先在React Compiler Playground上建立一個最小可重現的範例,並將其包含在您的錯誤報告中。您可以在facebook/react儲存庫中開啟問題。

您也可以申請成為 React Compiler 工作小組的成員,並在其中提供意見反應。請參閱README 以瞭解更多加入的詳細資訊

編譯器會假設什麼?

React Compiler 假設您的程式碼

  1. 是有效的、語義化的 JavaScript。
  2. 在存取可為空/可選的值和屬性之前,會測試它們是否已定義(例如,如果使用 TypeScript,則啟用strictNullChecks),例如 if (object.nullableProperty) { object.nullableProperty.foo } 或使用 optional-chaining object.nullableProperty?.foo
  3. 遵循React 的規則

React Compiler 可以靜態驗證許多 React 規則,並在偵測到錯誤時安全地跳過編譯。要查看錯誤,我們也建議安裝eslint-plugin-react-compiler

如何知道我的元件已最佳化?

React Devtools (v5.0+) 內建支援 React Compiler,並會在經過編譯器最佳化的元件旁邊顯示「Memo ✨」標記。

編譯後某些功能無法正常運作

如果您已安裝 eslint-plugin-react-compiler,編譯器會在您的編輯器中顯示任何違反 React 規則的情況。發生這種情況時,表示編譯器已跳過最佳化該元件或 hook。這是完全正常的,編譯器可以恢復並繼續最佳化程式碼庫中的其他元件。您不必立即修正所有 ESLint 違規。您可以按照自己的步調解決它們,以增加被最佳化的元件和 hook 的數量。

然而,由於 JavaScript 具有彈性和動態的特性,因此無法全面偵測所有情況。在這些情況下,可能會發生錯誤和未定義的行為,例如無限迴圈。

如果您的應用程式在編譯後無法正常運作,且您沒有看到任何 ESLint 錯誤,則可能是編譯器錯誤地編譯了您的程式碼。要確認這一點,請嘗試透過 "use no memo" 指令 強制停用您認為可能相關的任何元件或 Hook,來嘗試解決問題。

function SuspiciousComponent() {
"use no memo"; // opts out this component from being compiled by React Compiler
// ...
}

注意事項

"use no memo"

"use no memo" 是一個暫時性的解決方案,讓您可以選擇不讓 React 編譯器編譯元件和 Hook。這個指令不像 "use client" 那樣是永久性的。

除非絕對必要,否則不建議使用此指令。一旦您選擇停用元件或 Hook,它將永遠被停用,直到指令被移除。這表示即使您修復了程式碼,編譯器仍然會略過編譯它,除非您移除該指令。

當您解決錯誤後,請確認移除停用指令會使問題再次出現。然後使用 React Compiler Playground 與我們分享錯誤報告(您可以嘗試將其簡化為一個小的重現範例,或者如果是開放原始碼,您也可以直接貼上整個程式碼),以便我們識別並協助修復問題。

其他問題

請參閱 https://github.com/reactwg/react-compiler/discussions/7