今話題となっているReact 19のSuspenseの変更内容を詳しく見てみました。
はじめに
以下のツイートが発端でした。
投稿者は、React Query のメインコントリビュータの tkdodo さんです。
React 19 に含まれる変更にある異変を気づきました。
変更は以下に該当します
react: Don’t prerender siblings of suspended component #26380
リリースノートに隅っこにありました。
この変更は RFC なしで含まれました。該当 PR は以下
What
React では、 Suspense を使って、非同期処理を行うコンポーネントやReact.lazy
によってコンポーネントの遅延ロード時に、読み込みを完了するまでフォールバックを表示させることができます。
以下のような実装があるとします。
import { lazy, Suspense, useState } from "react";
const AvatarComponent = lazy(() => import("./AvatarComponent"));
const InfoComponent = lazy(() => import("./InfoComponents.tsx"));
const MoreInfoComponent = lazy(() => import("./MoreInfoComponent.tsx"));
export default function App() {
const [details, setDetails] = useState(false);
return (
<div className='App'>
{!details && <button onClick={() => setDetails(true)}>CLICK ME</button>}
{details && (
<Suspense fallback={<div className='loader'></div>}>
<AvatarComponent />
<InfoComponent />
<MoreInfoComponent />
</Suspense>
)}
</div>
);
}
コンポーネントたちを遅延ロードして、fetch されレンダリングされるまで、Suspense でロード中の状態を表示することをしています。
React 18 までは、2つのコンポーネントをまとめてひとつの Suspense に囲んでも、コンポーネントファイルを取得する fetch 処理は並行に行われました。
React 19 からはそうでなくなる予定です。
React 19 では、2つのコンポーネントをまとめてひとつの Suspense に囲むと、コンポーネントの fetch が並行に行われず、ウォーターフォール状態になります。
レンダリングが完了するまで以前より長かったこと体感できました。
一つの Suspense に囲んだコンポーネントの中で非同期処理を行うコンポーネントも影響しますね。
例は tkdodo さんが用意したものを参照します。
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<React.Suspense fallback={<p>...</p>}>
<RepoData name='tanstack/query' />
<RepoData name='tanstack/table' />
</React.Suspense>
</QueryClientProvider>
);
}
<RepoData/>
というコンポーネントは、内側でuseSuspenseQuery
を使って fetch を行なっています。
React 18 では並行に fetch を行なっています。
React 19 では、fetch が並行に行われず、ウォーターフォール状態になること確認できます。
これからどうするの
修正PR の記載の通り、fetch を hoist する、ようは Suspend をするコンポーネントのレンダリング処理以前に使うデータを取得しろのことですね。RSC 使える framework は、RSC で データを fetch。Client オンリーのアプリは、route loaders やハイレベルのコンポーネントで fetch を行うことを呼びかけています。
React チームがどう対応するかは、注目です 👀
Discussion