使用 React.lazy 和 Suspense 分割程式碼

您不必向使用者提供不必要的程式碼,因此請分割套件,確保這種情況不會發生!

React.lazy 方法可讓您使用動態匯入,輕鬆在元件層級分割 React 應用程式的程式碼。

import React, { lazy } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const DetailsComponent = () => (
  <div>
    <AvatarComponent />
  </div>
)

這種報表有哪些優點?

大型 React 應用程式通常包含許多元件、公用程式方法和第三方程式庫。如果未嘗試在需要時才載入應用程式的不同部分,則使用者載入第一個頁面時,系統就會傳送單一大型 JavaScript 套件。這可能會大幅影響網頁效能。

React.lazy 函式提供內建方法,可將應用程式中的元件分成不同的 JavaScript 區塊,幾乎不需費力。接著,您可以將其與 Suspense 元件結合,處理載入狀態。

Suspense (懸疑)

向使用者傳送大型 JavaScript 酬載的問題,在於網頁載入完成所需的時間,尤其是在裝置和網路連線較弱的情況下。因此,程式碼分割和延遲載入非常實用。

不過,透過網路擷取程式碼分割元件時,使用者一律會遇到輕微延遲,因此顯示實用的載入狀態非常重要。搭配使用 React.lazy 與 Suspense 元件,有助於解決這個問題。

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
  </Suspense>
)

Suspense 接受 fallback 元件,可讓您將任何 React 元件顯示為載入狀態。以下範例說明這項機制的運作方式。只有在點選按鈕時,系統才會算繪顯示圖片,並要求擷取暫停的 AvatarComponent 所需的程式碼。在此期間,系統會顯示備用載入元件。

在這裡,構成 AvatarComponent 的程式碼很小,因此載入旋轉圖示只會顯示一小段時間。較大的元件可能需要較長的載入時間,尤其是在網路連線不穩定的情況下。

以下舉例說明:

  • 如要預覽網站,請按下「View App」,然後按下「Fullscreen」全螢幕。
  • 按下「Control + Shift + J」鍵 (在 Mac 上為「Command + Option + J」鍵) 開啟開發人員工具。
  • 按一下 [網路] 分頁標籤。
  • 按一下「Throttling」下拉式選單 (預設為「No throttling」),選取「Fast 3G」。
  • 在應用程式中點選「Click Me」按鈕。

載入指標現在會顯示更久。請注意,構成 AvatarComponent 的所有程式碼都是以個別區塊的形式擷取。

開發人員工具網路面板,顯示正在下載一個 chunk.js 檔案

暫停多個元件

Suspense 的另一項功能是可暫停載入多個元件,即使這些元件都採用延遲載入。

例如:

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
    <InfoComponent />
    <MoreInfoComponent />
  </Suspense>
)

這種方法非常實用,可延遲多個元件的算繪作業,同時只顯示單一載入狀態。所有元件擷取完畢後,使用者就能同時看到所有元件。

您可以透過下列嵌入內容查看:

如果沒有這個功能,就很容易遇到交錯載入的問題,也就是 UI 的不同部分會逐一載入,且每個部分都有自己的載入指標。這可能會讓使用者體驗更不流暢。

處理載入失敗

Suspense 可讓您在背景發出網路要求時,顯示暫時的載入狀態。但如果這些網路要求因故失敗,該怎麼辦?您可能處於離線狀態,或網頁應用程式嘗試延遲載入過時的版本網址,而該網址在伺服器重新部署後已無法使用。

React 有一個標準模式,可妥善處理這類載入失敗:使用錯誤邊界。如說明文件所述,任何 React 元件只要實作 static getDerivedStateFromError() 或 componentDidCatch() 生命週期方法 (或兩者),即可做為錯誤邊界。

如要偵測及處理延遲載入失敗,您可以使用父項元件包裝 Suspense 元件,做為錯誤邊界。在錯誤邊界的 render() 方法中,如果沒有錯誤,可以照常算繪子項;如果發生錯誤,則算繪自訂錯誤訊息:

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {hasError: false};
  }

  static getDerivedStateFromError(error) {
    return {hasError: true};
  }

  render() {
    if (this.state.hasError) {
      return <p>Loading failed! Please reload.</p>;
    }

    return this.props.children;
  }
}

const DetailsComponent = () => (
  <ErrorBoundary>
    <Suspense fallback={renderLoader()}>
      <AvatarComponent />
      <InfoComponent />
      <MoreInfoComponent />
    </Suspense>
  </ErrorBoundary>
)

結論

如果不確定該從何處開始對 React 應用程式套用程式碼分割,請按照下列步驟操作:

  1. 從路徑層級開始。路由是找出應用程式可分割點的最簡單方法。React 文件說明如何搭配使用 Suspense 和 react-router。
  2. 找出網站網頁上只有在特定使用者互動 (例如點擊按鈕) 時才會算繪的大型元件。分割這些元件可盡量減少 JavaScript 酬載。
  3. 建議您分割其他不在螢幕上且對使用者不重要的內容。