Suspenseを久しく使っていないので、再履修しておこうと思います。
公式 : Suspense
そもそもサスペンスの意味は?
宙ぶらりんの状態、あやふや、どっちつかず、不安、気がかり、持続的緊張感、はらはらする状態
weblio
(サスペンス映画とか言われますね。)
Suspense
<Suspense>
を使うことで、子要素が読み込みを完了するまでフォールバックを表示させることができます。
なるほど、子要素が重い時、読み込みに時間がかかる時にSuspenseを使って別の何かを出すことができるということですね。
propsは2つ
- children
- レンダーしようとしている実際の UI (コンポーネント)
- fallback
- childrenのレンダーが完了するまで代わりにレンダーされるもの。
- ローディングスピナやスケルトン
- childrenのレンダーが完了するまで代わりにレンダーされるもの。
// 実装イメージ
<Suspense fallback="ローディング">
<SomeComponent />
</Suspense>
注意点
React は、初回マウントが成功するより前にサスペンドしたレンダーに関しては、一切の state を保持しま〜...
長いですね、、つまり下記の4つです。
初回マウント前のサスペンド
コンポーネントが初回マウント前にサスペンドすると、Reactはその時点のstateを保持せず、再度レンダリングを最初からやり直します。
// Suspenseのchildren
const SomeComponent = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
if (!data) {
// 一旦ここまで処理は進むけど、サスペンドが発生。再レンダリングされる。
// 次のレンダリングはデータの取得を待ってからとなる。
throw new Promise(resolve => setTimeout(resolve, 1000));
}
return <div>{data}</div>;
}
サスペンドとフォールバック
一度表示されたコンテンツが再度サスペンドした場合、通常はフォールバックが再び表示されます。ただし、この更新がstartTransitionやuseDeferredValueによって引き起こされた場合はしません。
レイアウトエフェクトのクリーンアップ
サスペンドによってコンテンツが隠された場合、Reactはレイアウトエフェクトをクリーンアップし、再表示時に再実行します。無駄なDOM操作はしないよということです。
最適化
Reactには、サスペンスと統合された自動的な最適化(ストリーミングサーバレンダリングや選択的ハイドレーション)が含まれています。
使用方法
<Suspense fallback="ローディング">
<SomeComponent />
</Suspense>
子要素がすべて読み込まれるまで、フォールバックを表示します。
補足
サスペンスコンポーネントをアクティブ化できるのはサスペンス対応のデータソースだけです。これには以下が含まれます~...
つまり、 サスペンス対応のフレームワークしか使えないよ
ということです。
Next.js, Remix, Relay は使えるみたいですね。
(useSWRやuseQueryを用いれば、フレームワーク問わず使えます)
コンテンツを一度にまとめて表示する
デフォルトでは、サスペンス内のすべてのツリーはひとつの単位として扱われます。例えば、以下のコンポーネントのうちどれかひとつでもデータ待ちでサスペンドしていれば、すべてがまとめてローディングインジケータに置き換わります。
ということは、Suspenseに巻き込みたくないコンポーネントは、<Suspense>
の外に出しておくのが良さそうですね。
非同期するコンポーネントが複数ある場合、準備が出来たものから表示したいのですが...。
ネストされたコンテンツをロード順に表示する
Suspenseの中にSuspenseを入れる...で、できるみたいです。
下記の実装では <Biography>
と <Albums>
はそれぞれ準備でき次第表示されます。
// 公式から引用
<Suspense fallback={<BigSpinner />}>
<Biography />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums />
</Panel>
</Suspense>
</Suspense>
新しいコンテンツのロード中に古いコンテンツを表示する
入力するたびにfallbackが表示されてしまうのは良くありません。
次の情報の準備が整うまで、1つ前の情報を出しっぱなしにしたいですね。

useDeferredValue フックを使うことで遅延されたバージョンのクエリ文字列を下に渡すことができます。(useDeferredValueのドキュメントも見ておかないと
終わりに
本日は <Suspense>
を再履修しました!
非同期データの読み込み中にユーザーにフォールバックUIを提供し、ローディングスピナーやプレースホルダーを表示することで、UIが空白にならないようできます!
ユーザーが「今何を待っているんだろう...」と不安にならないよう、状態を可視化してあげることがとても大事ですね。
Reactでの開発を進める際には、適切な場所で<Suspense>
を使って、アプリケーションのパフォーマンスとユーザーエクスペリエンスを最適化しましょう!
合わせてstartTransitionと、useDeferredValueもまた学んでおきましょう〜!