使用 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.lazySuspense 元件搭配使用有助於解決這個問題。

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 的程式碼很小,因此載入旋轉圖示只會短暫顯示。較大型的元件可能需要較長時間載入,尤其是在網路連線品質不佳時。

如要更明確地示範這項功能的運作方式:

  • 如要預覽網站,請按下「查看應用程式」,然後按下「全螢幕」圖示 全螢幕
  • 按下 `Control+Shift+J 鍵 (在 Mac 上為 Command+Option+J 鍵) 開啟開發人員工具。
  • 按一下 [網路] 分頁標籤。
  • 點選「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. 從路線層級開始。路徑是識別可以分割的應用程式點最簡單的方法。如要瞭解如何搭配使用 Suspensereact-router,請參閱 React 文件
  2. 找出網站上只有特定使用者互動 (例如點選按鈕) 才會顯示的大型元件。分割這些元件可盡量減少 JavaScript 酬載。
  3. 請考慮分割畫面外且對使用者來說不重要的其他項目。