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 ペイロードをユーザーに送信する場合の問題は、特に低性能のデバイスやネットワーク接続で、ページの読み込みが完了するまでに時間がかかることです。これが、コード分割と遅延読み込みが非常に有用である理由です。

ただし、コード分割コンポーネントがネットワーク経由でフェッチされる際には、常にわずかな遅延が発生するため、有用な読み込み状態を表示することが重要です。この問題を解決するには、Suspense コンポーネントで React.lazy を使用します。

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`)を押して、DevTools を開きます。
  • [ネットワーク] タブをクリックします。
  • [スロットリング] プルダウンをクリックします。デフォルトでは [スロットリングなし] に設定されています。[高速 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. 画面外にあり、ユーザーにとって重要でないその他のコンテンツは分割することを検討してください。