ReactのFragment・StrictMode・Suspenseとlazy:組み込み機能の使い方
ReactのFragment・StrictMode・Suspense・lazyを用語集の実装で解説。key付きFragment、遅延読み込み、開発時の追加チェック、待機と失敗の違いを学びます。
ReactにはHooks以外にも、画面のまとまりや開発時の検証、読み込み待ちを扱う機能があります。この記事ではFragment、StrictMode、Suspenseとlazyを使い、用語集を必要になってから読み込む小さなアプリを作ります。
4つの機能の役割
構造と検証を担当する機能
Fragmentは、余分なDOM要素を増やさずに複数の要素をまとめます。StrictModeは、配下のコンポーネントに対する開発時の追加チェックを有効にします。どちらも装飾用のHTML要素を作るものではありません。
読み込みを担当する機能
lazyはコンポーネントのコードを必要になるまで読み込みません。Suspenseは、配下の対応する処理が待機している間、fallbackとして指定した表示を使います。今回はlazyによるコード読み込みと組み合わせます。
Fragmentで用語と説明をまとめる
Glossary.tsxを作成する
ViteのReact・TypeScriptプロジェクトでsrc/Glossary.tsxを作成します。dlの中に用語のdtと説明のddを並べるため、余分なdivを追加せずにまとめます。
import { Fragment } from 'react';
const terms = [
{ id: 'props', term: 'props', description: '親から子へ渡す入力です。' },
{ id: 'state', term: 'state', description: 'コンポーネントが保持する状態です。' },
{ id: 'hook', term: 'Hook', description: 'Reactの機能を利用するための関数です。' },
];
export default function Glossary() {
return (
<section aria-labelledby="glossary-heading">
<h2 id="glossary-heading">Reactの用語集</h2>
<dl>
{terms.map((item) => (
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
))}
</dl>
</section>
);
}
省略記法とkeyの関係
<>...</>もFragmentの記法ですが、そこへkeyを指定することはできません。今回のように一覧の各組へkeyが必要なら、Fragmentをimportして明示的に書きます。
keyには用語データのIDを使います。Fragmentを使っても、一覧に安定したkeyが必要な点は変わりません。
lazyとSuspenseで用語集を読み込む
App.tsxで読み込み境界を作る
src/App.tsxを作成します。lazyはコンポーネントの外側で宣言します。ボタンを押してGlossaryを初めて描画するときに、モジュールの読み込みが始まります。
import { lazy, Suspense, useState } from 'react';
const Glossary = lazy(() => import('./Glossary'));
export default function App() {
const [showGlossary, setShowGlossary] = useState(false);
return (
<main>
<h1>必要なときに読むReact用語集</h1>
<button
type="button"
aria-expanded={showGlossary}
aria-controls="glossary-panel"
onClick={() => setShowGlossary((current) => !current)}
>
{showGlossary ? '用語集を閉じる' : '用語集を開く'}
</button>
<div id="glossary-panel" hidden={!showGlossary}>
{showGlossary && (
<Suspense fallback={<p role="status">用語集を読み込んでいます。</p>}>
<Glossary />
</Suspense>
)}
</div>
</main>
);
}
default exportと宣言位置を確認する
この書き方では、読み込むモジュールのdefaultがコンポーネントである必要があります。先ほどのGlossary.tsxはexport defaultで定義しています。
lazyの宣言をAppの関数内に置くと、レンダーのたびに別のコンポーネントとして扱われ、状態がリセットされる原因になります。モジュールのトップレベルで宣言してください。
StrictModeで開発中に検証する
main.tsxを確認する
src/main.tsxでStrict Modeを有効にします。Viteテンプレートですでに設定されている場合は、重ねて追加する必要はありません。ここでは全体の例を示します。
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.getElementById('root');
if (!container) throw new Error('root要素が見つかりません。');
createRoot(container).render(
<StrictMode>
<App />
</StrictMode>
);
追加実行で問題を見つける
Strict Modeでは、開発中にレンダーやEffectの処理が追加で実行されることがあります。propsを直接変更している、Effectが登録したイベントを解除していない、といった問題に気づくためのチェックです。
追加実行を隠すためにStrict Modeを外す前に、レンダーの純粋性やEffectのクリーンアップを確認します。このチェックによって本番でもすべてが二重に実行されるわけではありません。
動作確認と実運用の注意
開閉と読み込みを確認する
- 初期表示では用語集の内容は表示されません。
- 開くボタンを押すと用語集が表示されます。
dlの直接の子がdtとddになり、Fragment用のHTML要素がないことを確認します。- 閉じると用語集が消え、もう一度開くと再表示されます。
- 読み込みが速い場合、待機表示は目で確認できないことがあります。
ネットワークの低速設定とキャッシュ無効化を使うと、初回のコード読み込みを観察しやすくなります。Viteの開発サーバーと本番ビルドでは配信されるファイル構成が異なります。最終的な分割はビルド成果物でも確認してください。
Suspenseはあらゆる通信を検出するわけではない
useEffectの中でfetchするだけでは、その通信をSuspenseが自動検出してfallbackを表示するわけではありません。今回は公式に対応しているlazyのコード読み込みを使っています。
データ取得にSuspenseを使う場合は、対応するフレームワークやライブラリの方式に従います。Promiseを適当に投げる自作の仕組みを、この例から推測して追加しないでください。
読み込み失敗と読み込み待ちは別に扱う
Suspenseのfallbackは待機中の表示です。コード読み込みが失敗した場合のエラー表示は、Error Boundaryなどで別途扱います。この最小例は開閉と遅延読み込みを示すもので、エラー時の再試行UIは実装していません。
再度開くときは、lazyが読み込んだモジュールを再利用できます。一方、この例は閉じるとコンポーネントを取り外すので、用語集に追加したローカルstateまで維持されるとは限りません。コードのキャッシュとコンポーネントのstateは区別します。
参考資料と関連記事
Hooks以外の機能も役割から選ぶ
部品の構造にはFragment、開発時の検証にはStrictMode、対応する待機表示にはSuspenseというように役割を分けます。stateやEffectの基礎は基本Hooks入門を参照してください。