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

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

ここでは、AvatarComponent を構成するコードが小さいため、読み込みスピナーは短時間しか表示されません。コンポーネントが大きいと、特にネットワーク接続が弱い場合に、読み込みに時間がかかることがあります。

この仕組みをよりわかりやすく説明するために、

  • サイトをプレビューするには、[View App] を押します。次に、[Fullscreen] 全画面表示 を押します。
  • 「Ctrl+Shift+J」(Mac の場合は「Command+Option+J」)を押して DevTools を開きます。
  • [ネットワーク] タブをクリックします。
  • [スロットリング] プルダウンをクリックします。デフォルトでは [スロットリングなし] に設定されています。[Fast 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>
)

これは、複数のコンポーネントのレンダリングを遅らせながら、単一の読み込み状態のみを表示する非常に便利な方法です。すべてのコンポーネントの取得が完了すると、ユーザーはそれらすべてが同時に表示されるのを確認できます。

これは、次の埋め込みで確認できます。

これがないと、段階的な読み込みの問題が発生しやすくなります。つまり、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 のドキュメントでは、Suspense を react-router と組み合わせて使用する方法が説明されています。
  2. 特定のユーザー操作(ボタンのクリックなど)でのみレンダリングされる、サイトのページ上の大きなコンポーネントを特定します。これらのコンポーネントを分割すると、JavaScript ペイロードを最小限に抑えることができます。
  3. 画面外にあり、ユーザーにとって重要でないものは、分割することを検討してください。