パフォーマンスの解錠:フロントエンドフレームワークにおけるレンダリング=フェッチ方式
Olivia Novak
Dev Intern · Leapcell

はじめに
進化し続けるフロントエンド開発の状況において、パフォーマンスとユーザーエクスペリエンスの最適化は、依然として最重要の課題です。データ取得の従来のアプローチは、しばしばウォーターフォール、空の状態、またはユーザーを苛立たせるスピナーにつながります。すべてのデータが到着する前でも、アプリケーションが意味のあるUI要素のレンダリングを開始でき、利用可能になり次第、ユーザーにシームレスにコンテンツをストリーミングできる世界を想像してみてください。これは、SuspenseやReact Server Components(RSC)のような機能が達成しようとしているビジョンであり、その中心には強力なパラダイムがあります:「レンダリング=フェッチ」。この記事では、「レンダリング=フェッチ」がどのように機能し、応答性が高く効率的なWebアプリケーションの新時代を切り開くかを探ります。
コアコンセプトの説明
「レンダリング=フェッチ」に深く入る前に、この革新的なパターンへの道を開くいくつかの基本的な概念を明確にしましょう。
データ取得ウォーターフォール: 従来の一枚ページアプリケーションでは、コンポーネントはマウント時にデータを取得することがよくあります。親コンポーネントがデータを取得し、その子コンポーネントが親のデータに基づいて独自のデータを取得する場合、これはリクエストのシーケンシャルチェーン、つまりデータ取得ウォーターフォールを形成します。各ステップは前のステップの完了を待ち、ロード時間を延長させます。
Suspense: Reactによって導入されたSuspenseは、コンポーネントが非同期データなどを待っている間にレンダリングを「一時停止」するためのメカニズムです。空白の画面を表示したり、ブール値でロード状態を手動で管理したりする代わりに、Suspenseを使用すると、データが取得されている間にフォールバックUI(スピナーのようなもの)を宣言的に指定できます。データが解決されると、実際のコンポーネントがレンダリングされます。
React Server Components(RSC): RSCはReactの画期的な機能であり、サーバーでコンポーネントをレンダリングし、結果のHTMLとクライアントコンポーネントをブラウザに送信できます。これにより、クライアントに送信されるJavaScriptの量を減らし、初期ページロード時間を改善し、SEOを強化することで、大幅なパフォーマンス上の利点が得られます。特に重要なのは、RSCはクライアントとサーバーの間の境界を曖昧にし、より効率的なデータ取得戦略を可能にすることです。
レンダリング=フェッチのメカニズム
「レンダリング=フェッチ」パターンは、データ取得が発生するタイミングと方法を根本的に変えます。コンポーネントのレンダリングが開始された後(例:useEffectフック内)にデータ取得を行うのではなく、データ取得はレンダリングプロセスと並行して、またはそれ以前に、早期に開始されます。その後、レンダリングは進行中のデータ取得にサブスクライブします。
仕組みは以下のとおりです。
- 早期のフェッチ開始: データリクエストは、理想的には初期レンダリングパスの前または最中に、できるだけ早くディスパッチされます。これは、ユーザーインタラクション、ルート変更、またはサーバーサイドの事前レンダリング中にトリガーされる可能性があります。
- フォールバック(Suspense)による即時レンダリング: コンポーネントはレンダリングを試みます。必要なデータがまだ利用できない場合、「一時停止」します。Reactは、Suspenseを介して、データが取得されている間に定義済みのフォールバックUIを表示します。この重要なステップにより、空白の画面が排除され、完全なコンテンツが準備できていない場合でも、アプリケーションは即座に応答できます。
- データとコンポーネントのストリーミング(RSC): RSCのコンテキストでは、サーバーはコンポーネントツリーの一部をレンダリングし、結果のHTMLとクライアントコンポーネントへの参照をブラウザにストリーミングできます。特定のツリー部分のデータがサーバーで利用可能になると、その部分がレンダリングされストリーミングされます。これにより、ブラウザはページ全体が準備できるのを待つのではなく、到着次第コンテンツを段階的に表示できます。
- 解決と再レンダリング: データ取得が完了すると、Suspenseは、新しく利用可能になったデータで一時停止されたコンポーネントの再レンダリングをオーケストレーションします。これはフルページリロードなしで行われ、スムーズでダイナミックなユーザーエクスペリエンスを提供します。
Suspenseを使用した概念実証の例
単純なデータ取得関数とReact Suspenseを使用した実際的な例を考えてみましょう。
// データフェッチャーユーティリティ const wrapPromise = (promise) => { let status = "pending"; let result; let suspender = promise.then( (r) => { status = "success"; result = r; }, (e) => { status = "error"; result = e; } ); return { read() { if (status === "pending") { throw suspender; // コンポーネントを一時停止します } else if (status === "error") { throw result; } else if (status === "success") { return result; } }, }; }; const fetchData = () => { console.log("ユーザーデータを取得中..."); return new Promise((resolve) => { setTimeout(() => { resolve({ name: "Alice", email: "alice@example.com" }); }, 2000); // ネットワーク遅延をシミュレート }); }; // 取得したデータを保持するリソースオブジェクト let userResource; const fetchUser = () => { if (!userResource) { userResource = wrapPromise(fetchData()); } return userResource; }; // ユーザーデータコンポーネント function UserProfile() { const user = fetchUser().read(); // まだ実行されていない場合はフェッチを開始し、それ以外の場合は一時停止します return ( <div> <h2>ユーザープロフィール</h2> <p>名前: {user.name}</p> <p>メール: {user.email}</p> </div> ); } // Suspenseを使用したAppコンポーネント export default function App() { // UserProfileがレンダリングされる前でも、早期にフェッチを開始します // これは単純な例です。実際のアプリでは、ルーターまたは親コンポーネントによってトリガーされる場合があります // fetchUser(); return ( <div> <h1>ようこそ!</h1> <Suspense fallback={<div>ユーザーデータをロード中...</div>}> <UserProfile /> </Suspense> </div> ); }
この例では:
fetchUser()が呼び出されます。データがまだ取得されていない場合、Promiseをラップして保存します。UserProfileがuserResourceからread()しようとすると、Promiseがまだ保留中の場合、Promiseをスローします。これが「一時停止」メカニズムです。SuspenseはこのスローされたPromiseをキャッチし、そのfallbackをレンダリングします。- Promiseが解決されると(2秒後)、Reactは実際のデータで
UserProfileを再レンダリングします。
ここでの鍵は、read()はレンダリング中に呼び出されますが、フェッチ自体は早期に開始された可能性があるということです。
React Server Componentsとのアプリケーション
RSCでは、「レンダリング=フェッチ」の概念はさらに強力になります。サーバーでデータを取得するサーバーコンポーネントを想像してみてください。
// app/page.js (サーバーコンポーネント) import { Suspense } from 'react'; import UserDetails from './UserDetails'; async function getUserData() { // これはサーバーで直接データを取得します const response = await fetch('https://api.example.com/users/1'); const data = await response.json(); return data; } export default async function Page() { // サーバーコンポーネントのレンダリング開始時にすぐにデータを取得します const userDataPromise = getUserData(); return ( <main> <h1>ダッシュボード</h1> <Suspense fallback={<p>ユーザー詳細をロード中...</p>}> {/* UserDetailsはサーバーコンポーネントからprops経由でデータを受け取るクライアントコンポーネントです */} <UserDetails userDataPromise={userDataPromise} /> </Suspense> {/* ユーザーデータに依存しない他のコンポーネントはすぐにレンダリングできます */} <SomeOtherComponent /> </main> ); } // app/UserDetails.jsx (クライアントコンポーネント) // このクライアントコンポーネントは、サーバーコンポーネントから渡されたPromiseを // 読み込むために、内部的に`use`フックなどを使用します。 // (`use`フックは多くのコンテキストでまだ実験的であるため、簡略化しています) import { use } from 'react'; export default function UserDetails({ userDataPromise }) { const user = use(userDataPromise); // Promiseを読み取ります(解決されていない場合は一時停止します) return ( <div> <h2>こんにちは、{user.name}さん!</h2> <p>あなたのID: {user.id}</p> </div> ); }
このRSCの例では:
getUserData()は、Pageのレンダリング開始と同時にサーバーで呼び出されます。userDataPromiseはUserDetailsクライアントコンポーネントに渡されます。- クライアントコンポーネントは
use(userDataPromise)を使用します。これはPromiseを読み取ります。Promiseがまだ解決されていない場合(例:ネットワーク遅延またはデータベースクエリ)、UserDetailsコンポーネントは一時停止します。 - ブラウザはすぐに
<h1>ダッシュボード</h1>とSomeOtherComponentのHTML、およびUserDetailsのfallbackを受け取ります。 userDataPromiseがサーバーで解決されるとすぐに、サーバーは完全にレンダリングされたUserDetailsHTMLチャンクをクライアントにストリーミングできるため、ページがシームレスに更新されます。これがRSCを使用したHTMLストリーミングの本質です。
利点とユースケース
SuspenseとRSCによって強化された「レンダリング=フェッチ」パターンは、いくつかの説得力のある利点を提供します。
- ウォーターフォールを排除: データフェッチを早期かつ並行して開始することで、累積的な待機時間を大幅に短縮します。
- ユーザーエクスペリエンスの向上: ユーザーは、一部のデータがまだロードされていても、より早く意味のあるコンテンツと即時のUI応答を目にします。これにより、ロードが速いという認識と、より流動的なインタラクションが得られます。
- パフォーマンスの向上: 特にRSCでは、サーバーでのレンダリングはクライアントサイドのJavaScriptを削減し、初期ロード時間を改善し、データソースに近い場所でのより効率的なデータ取得を可能にします。
- ロード状態の簡素化: 開発者は、すべてのデータ依存関係に対して
isLoadingブール値や条件付きレンダリングを手動で管理する必要がなくなり、よりクリーンで宣言的なコードにつながります。 - 段階的な機能強化: SPAのインタラクティビティを備えつつも、従来のWebサイトがコンテンツを表示していた方法と同様に、コンテンツをストリーミングして利用可能になったとおりに表示できます。
このパターンは以下に最適です。
- 複雑なダッシュボード: さまざまなパネルがさまざまなデータソースに依存する場合。
- Eコマース商品ページ: 一部のデータがまだロードされている間でも、商品詳細、レビュー、関連アイテムを表示する。
- ソーシャルフィード: ユーザーがスクロールするにつれて新しいコンテンツを継続的にロードする。
- 高速な初期ロードとインタラクティブなコンテンツを必要とするあらゆるアプリケーション。
結論
「レンダリング=フェッチ」は、単なるデータ取得戦略ではありません。インタラクティブなWebアプリケーションを構想し構築する方法における根本的なシフトです。データ取得をコンポーネントのレンダリングライフサイクルから分離し、SuspenseやReact Server Componentsのようなメカニズムを採用することにより、フロントエンドフレームワークは開発者が卓越したパフォーマンスとユーザーフレンドリーなエクスペリエンスを構築できるようにします。このパラダイムは、アプリケーションがより早く意味のあるものを表示できるようにし、真に段階的で応答性の高いインタラクションを提供し、Web開発の未来を再形成します。

