Suspense
<Suspense>
コンポーネントは React の最上位 API に含まれる。<Suspense>
は、React 16.6以降で導入されたコンポーネントで、非同期な操作(たとえば、データのフェッチやコードの分割)をサポートするために使用される。
<Suspense>
コンポーネントは、React アプリケーションで非同期な処理が完了するまで待機し、その間に指定されたフォールバック(代替)コンポーネントを表示することができる。
これは、特にコードの分割(Code Splitting)やデータの遅延読み込みなどの場面で有用である。
以下公式ドキュメントより引用。
<Suspense fallback={<Loading />}>
<SomeComponent /> //children
</Suspense>
children: レンダーしようとしている実際の UI です。children がレンダー中にサスペンド(suspend, 一時中断)すると、サスペンスバウンダリは fallback のレンダーに切り替わります。
fallback: 実際の UI がまだ読み込みを完了していない場合に、その代わりにレンダーする代替 UI です。有効な React ノードであれば何でも受け付けますが、現実的には、フォールバックとは軽量なプレースホルダビュー、つまりローディングスピナやスケルトンのようなものです。children がサスペンドすると、サスペンスは自動的に fallback に切り替わり、データが準備できたら children に戻ります。fallback 自体がレンダー中にサスペンドした場合、親のサスペンスバウンダリのうち最も近いものがアクティブになります。
すでに表示されているコンテンツが隠れるのを防ぐ
下記のコードの場合、コンポーネントがサスペンドすると、直近の親のサスペンスバウンダリがフォールバック表示に切り替わる。すでに何らかのコンテンツを表示していた場合、これによりユーザ体験が不快になる可能性がある。
import { Suspense, useState } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';
export default function App() {
return (
<Suspense fallback={<BigSpinner />}>
<Router />
</Suspense>
);
}
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
setPage(url);
}
let content;
if (page === '/') {
content = (
<IndexPage navigate={navigate} />
);
} else if (page === '/the-beatles') {
content = (
<ArtistPage
artist={{
id: 'the-beatles',
name: 'The Beatles',
}}
/>
);
}
return (
<Layout>
{content}
</Layout>
);
}
function BigSpinner() {
return <h2>🌀 Loading...</h2>;
}
サイト全体のレイアウトが BigSpinner に置き換えられてしまう。
これを防ぐため、ナビゲーションの state 更新をstartTransition
でトランジションとしてマークすることができる。
import { Suspense, startTransition, useState } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';
export default function App() {
return (
<Suspense fallback={<BigSpinner />}>
<Router />
</Suspense>
);
}
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
let content;
if (page === '/') {
content = (
<IndexPage navigate={navigate} />
);
} else if (page === '/the-beatles') {
content = (
<ArtistPage
artist={{
id: 'the-beatles',
name: 'The Beatles',
}}
/>
);
}
return (
<Layout>
{content}
</Layout>
);
}
function BigSpinner() {
return <h2>🌀 Loading...</h2>;
}
startTransition
を使用することで、React に対して state の遷移が緊急のものではなく、既に表示されている内容を隠すよりも前のページを表示し続ける方が良いと伝えることができる。
startTransitionについてより詳しく知る
startTransition
は、React18から導入された新しいAPIであり、Reactの非同期な更新を制御するためのもの。
この API は、ユーザーエクスペリエンスを向上させるために非同期な操作をスケジューリングするために使用される。
startTransition
を使うと、Reactは非同期な更新の開始を通知し、その操作がブラウザのアイドルタイムで行われるようにスケジューリングする。
これにより、リソースが利用可能なときに非同期な操作が実行され、ユーザーエクスペリエンスが向上することが期待される。例えば、ページ遷移や非同期データの読み込みなどがこれに該当する。
startTransition
を使って非同期なトランジションを開始することができる。
import { startTransition, useState } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
const fetchData = async () => {
// 非同期なデータの取得
const result = await fetchDataFromServer();
// startTransitionを使用して非同期なトランジションを開始
startTransition(() => {
setData(result);
});
};
return (
<div>
<button onClick={fetchData}>Fetch Data</button>
{data && <div>{data}</div>}
</div>
);
}
この例では、fetchData 関数が非同期なデータを取得し、startTransition 内で新しいデータを設定している。これにより、データの取得が非同期であるときに、トランジションが開始され、ユーザーエクスペリエンスが向上する。
重要なのは、startTransition
は非同期な更新をスケジューリングするだけであり、即座にコンポーネントの再レンダリングをトリガーしない点。そのため、Reactはアイドルタイムに非同期な操作を実行し、完了後に適切なタイミングでコンポーネントを再レンダリングする。
一般的なReactの挙動では、状態が変更されると、すぐにコンポーネントが再レンダリングされる。しかし、非同期な操作が多くの計算資源やネットワークリソースを必要とする場合、その処理が完了するまでに時間がかかり、ユーザーエクスペリエンスが悪化する可能性がある。
startTransition
を使用することで、Reactは非同期な操作の完了を待たずに、一旦コンポーネントの再レンダリングを保留し、アイドル状態になったときに再レンダリングを実行する。これにより、ユーザーがブラウジングしている間に滑らかで応答性の高いエクスペリエンスが提供される。
startTransition
はReactに「この非同期な操作は優先的に処理してください。ですが、すぐに再レンダリングはしないでください。アイドル状態で実行してください」と指示するための手段である。
トランジションが進行中であることを示す
startTransition
をuseTransition
に置き換えることで、ブーリアン型のisPending
値が得られる。
import { Suspense, useState, useTransition } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';
export default function App() {
return (
<Suspense fallback={<BigSpinner />}>
<Router />
</Suspense>
);
}
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
let content;
if (page === '/') {
content = (
<IndexPage navigate={navigate} />
);
} else if (page === '/the-beatles') {
content = (
<ArtistPage
artist={{
id: 'the-beatles',
name: 'The Beatles',
}}
/>
);
}
return (
<Layout isPending={isPending}>
{content}
</Layout>
);
}
function BigSpinner() {
return <h2>🌀 Loading...</h2>;
}
startTransitionを使用して更新を低緊急度としてマークすることができる。
また、 useTransition に置き換えることでナビゲーションが進行中であることを視覚的に示すことができる。
トランジション中、Reactは十分なデータがロードされるまで待機し、不要なフォールバックが表示されるのを防ぐことが重要。
参考