Kodokon kodokon.com

Suspense、lazy、そしてコード分割

サスペンションのメカニズム、コンポーネントの遅延読み込み、バンドルの分割を習得します。

9 分 · 3 問

このレッスンを Kodokon で開く

Suspense はあまり知られていないメカニズムに依存しています。まだデータやコードを持たないコンポーネントは、そのレンダリングを サスペンド(中断) します。内部的には、現在の実装は Promise をスローし、React はそれをちょうどエラーを傍受するのと同じように傍受します。React はその後 最も近い <Suspense> 境界 まで遡り、その fallback を表示し、Promise が解決されると サブツリーのレンダーを再実行 します。これは実装の詳細(自分で Promise をスローしてはいけません)ですが、観測できるすべての挙動を説明します。境界は非同期処理に対する宣言的な catch として振る舞うのです。歴史的なユースケースは React.lazy による コード分割 です。

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

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

function Dashboard() {
  return (
    <Suspense fallback={<Spinner />}>
      <Chart points={data} />
    </Suspense>
  );
}
Chart のコードは最初のレンダー時にのみダウンロードされます。

lazy は、default エクスポートを持つモジュール に解決される Promise を返す関数を期待します。分割そのものはバンドラーの仕事です。各動的 import() は分割点となり、別個のチャンク を生成して、必要に応じて読み込まれます。実際のコードベースでよくある微妙な点: あなたのコンポーネントはしばしば 名前付きエクスポート です。その場合、モジュールをその場で適合させる必要があります。

JAVASCRIPT
const Chart = lazy(() =>
  import('./charts').then((module) => ({
    default: module.Chart,
  }))
);
名前付きエクスポートを React.lazy の契約に適合させる。

本番環境で厄介な部分: ナビゲーション の際、すでに表示されているコンテンツが再びサスペンドし(新しいルート、新しいチャンク)、突然 fallback に置き換えられることがあります。React 18 以降、startTransition でラップされた更新はこの挙動を変えます。React は新しいレンダーがバックグラウンドでサスペンドしている間、それまでのインターフェースを画面に残し 、まだ一度も表示されていないコンテンツについてのみ fallback を表示します。境界は深さ方向にも組み合わせられます。複数のネストした <Suspense> 境界により、グローバルなスケルトンから詳細に至るまで、インターフェースをゾーンごとに段階的に表示できます。

JSX
const [isPending, startTransition] = useTransition();

function openTab(nextTab) {
  startTransition(() => {
    setTab(nextTab);
  });
}
// The current tab stays visible while the new
// one loads, instead of dropping back to the fallback.
トランジションはナビゲーション中に fallback へ戻るのを防ぎます。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. React.lazy に渡す関数は何を返す必要がありますか?
    • React コンポーネントを直接
    • default エクスポートを持つモジュールに解決される Promise
    • オブジェクト { component, loading }
    • ファイルパスを含む文字列
  2. ナビゲーションがサスペンドしたときに、すでに表示されているコンテンツが fallback に置き換えられるのをどうやって防ぎますか?
    • state 更新を startTransition でラップする
    • Suspense 境界に fallback={null} を渡す
    • useEffect を使ってナビゲーションを遅延させる
    • 親の Suspense 境界を削除する
  3. コードを別個のチャンクに分割する作業を実際に行うのは誰ですか?
    • React。コミットフェーズ中に
    • ブラウザ。HTTP キャッシュを介して
    • バンドラー。各動的 import() から