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 コンポーネントと組み合わせることで、読み込み状態を処理できます。

サスペンス

大きな 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>
)

Suspensefallback コンポーネントを受け入れます。これにより、任意の React コンポーネントを読み込み状態として表示できます。次の例は、この仕組みを示しています。アバターはボタンがクリックされたときにのみレンダリングされ、その時点で一時停止された AvatarComponent に必要なコードを取得するリクエストが作成されます。その間、フォールバックの読み込みコンポーネントが表示されます。

ここでは、AvatarComponent を構成するコードが小さいため、読み込みスピナーの表示時間が短くなっています。コンポーネントが大きいほど、読み込みに時間がかかります。特にネットワーク接続が弱い場合は、時間がかかります。

この仕組みをよりわかりやすく説明するために、次の例をご覧ください。

  • サイトをプレビューするには、[アプリを表示] を押し、[全画面表示] 全画面表示 を押します。
  • `Ctrl+Shift+J`(Mac の場合は `Command+Option+J`)を押して、デベロッパー ツールを開きます。
  • [ネットワーク] タブをクリックします。
  • [スロットリング] プルダウンをクリックします。デフォルトでは [スロットリングなし] に設定されています。[高速 3G] を選択します。
  • アプリの [Click Me] ボタンをクリックします。

読み込みインジケーターの表示時間が長くなります。AvatarComponent を構成するすべてのコードが個別のチャンクとしてフェッチされていることに注目してください。

1 つの chunk.js ファイルがダウンロードされていることを示す DevTools の [ネットワーク] パネル

複数のコンポーネントを一時停止する

Suspense のもう 1 つの機能は、すべてのコンポーネントが遅延読み込みされる場合でも、複数のコンポーネントの読み込みを一時停止できることです。

次に例を示します。

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>
)

これは、複数のコンポーネントのレンダリングを遅延させながら、1 つの読み込み状態のみを表示する非常に便利な方法です。すべてのコンポーネントのフェッチが完了すると、ユーザーはすべてのコンポーネントを同時に表示できます。

次の埋め込みで確認できます。

これがないと、読み込みの遅延の問題が発生しやすくなります。つまり、UI のさまざまな部分が次々と読み込まれ、それぞれに独自の読み込みインジケーターが表示されるようになります。これにより、ユーザー エクスペリエンスがより不快に感じられる可能性があります。

読み込みエラーを処理する

Suspense を使用すると、ネットワーク リクエストがバックグラウンドで行われている間、一時的な読み込み状態を表示できます。しかし、何らかの理由でネットワーク リクエストが失敗した場合はどうなるでしょうか?オフラインになっているか、ウェブアプリが古いバージョン付き URL を遅延読み込みしようとしていて、サーバーの再デプロイ後に利用できなくなっている可能性があります。

React には、エラー境界を使用して、このような読み込みエラーを適切に処理するための標準パターンがあります。ドキュメントで説明されているように、ライフサイクル メソッド static getDerivedStateFromError() または componentDidCatch()(あるいは両方)を実装している場合、React コンポーネントはエラー境界として機能します。

遅延読み込みの失敗を検出して処理するには、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 ドキュメントには、Suspensereact-router とともに使用する方法が記載されています。
  2. サイトのページで、特定のユーザー操作(ボタンのクリックなど)でのみレンダリングされる大きなコンポーネントを特定します。これらのコンポーネントを分割すると、JavaScript ペイロードを最小限に抑えることができます。
  3. 画面外にあり、ユーザーにとって重要でないものは、分割することを検討してください。