Relayのアプローチは、アプリケーションの作成において、最適なランタイムパフォーマンスとアプリケーションの保守性の独自の組み合わせを実現します。この記事では、ほとんどのアプリがデータ取得において行わなければならないトレードオフについて説明し、次にRelayのアプローチによってこれらのトレードオフを回避し、複数のトレードオフ次元で最適な結果を得ることができる方法について説明します。
ReactなどのコンポーネントベースのUIシステムでは、UIツリーのどこにデータを取得するかを決定することが重要です。データ取得はUIツリーの任意の場所で行うことができますが、トレードオフを理解するために、両方の極端な例を考えてみましょう。
- リーフノード:データを使用する各コンポーネント内で直接データを取得します。
- ルートノード:UIのルートですべてのデータを取得し、プロップドリリングを使用してリーフノードにデータを渡します。
UIツリーのどこにデータを取得するかは、アプリケーションのパフォーマンスと保守性の複数の側面に影響します。残念ながら、単純なデータ取得では、どちらの極端な方法もすべての側面で最適ではありません。これらの側面を見て、データ取得をリーフノードに近づけるにつれて改善されるものと、ルートノードに近づけるにつれて改善されるものを検討しましょう。
読み込みエクスペリエンス
- 🚫 リーフノード:個々のノードがデータを取得する場合、UIは複数のリクエストラウンドトリップを連続して行う必要があり(ウォーターフォール)、UIの各レイヤーは親レイヤーのレンダリングをブロックするため、リクエストカスケードが発生します。さらに、複数のコンポーネントが同じデータを使用する場合、同じデータを複数回取得することになります。
- ✅ ルートノード:すべてのデータがルートで取得される場合、単一のリクエストを行い、重複データやカスケードリクエストなしでUI全体をレンダリングします。
サスペンスカスケード
- 🚫 リーフノード:個々のコンポーネントが別々にデータを取得する必要がある場合、各コンポーネントは最初のレンダリング時に中断されます。Reactの現在の実装では、中断解除により、最も近い親サスペンス境界から再レンダリングされます。つまり、最初のロード中に製品コンポーネントコードをO(n)回評価する必要があります(nはツリーの深さ)。
- ✅ ルートノード:すべてのデータがルートで取得される場合、1回だけ中断し、製品コンポーネントコードを1回だけ評価します。
構成可能性
- ✅ リーフノード:既存のコンポーネントを新しい場所に使用するのは、レンダリングするだけなので簡単です。コンポーネントの削除は、レンダリングしないのと同じくらい簡単です。同様に、データ依存関係の追加/削除は完全にローカルで行うことができます。
- 🚫 ルートノード:既存のコンポーネントを別のコンポーネントの子として追加するには、そのコンポーネントを含むすべてのクエリを更新して新しいデータを取得し、新しいデータをすべての中間レイヤーに渡す必要があります。同様に、コンポーネントを削除するには、これらのデータ依存関係を各ルートコンポーネントにトレースし、削除したコンポーネントがそのデータの最後のコンシューマーであったかどうかを判断する必要があります。同じダイナミクスは、既存のコンポーネントに新しいデータを追加/削除する場合にも適用されます。
詳細な更新
- ✅ リーフノード:データが変更されると、そのデータを読み取る各コンポーネントは個別に再レンダリングできるため、影響を受けないコンポーネントを再レンダリングする必要がありません。
- 🚫 ルートノード:すべてのデータがルートから生成されるため、データが更新されると、常にルートコンポーネントが更新され、コンポーネントツリー全体の高価な再レンダリングが強制されます。
Relay
RelayはGraphQLフラグメントとコンパイラのビルドステップを活用して、より最適な代替手段を提供します。Relayを使用するアプリでは、各コンポーネントはGraphQLフラグメントを定義し、必要なデータを宣言します。これには、コンポーネントがレンダリングする具体的な値と、レンダリングする各直接子コンポーネントのフラグメント(名前で参照)の両方が含まれます。
ビルド時に、Relayコンパイラはこれらのフラグメントを収集し、アプリケーションの各ルートノードに対して単一のクエリを構築します。このアプローチが上記の各次元でどのように機能するかを見てみましょう。
- ✅ 読み込みエクスペリエンス - コンパイラで生成されたクエリは、単一のラウンドトリップですべての必要なデータをフェッチします。
- ✅ サスペンスカスケード - すべてのデータは単一のリクエストでフェッチされるため、1回だけ中断され、ツリーのルートで中断されます。
- ✅ 構成可能性 - 子コンポーネントのレンダリングに必要なフラグメントデータを含む、コンポーネントからのデータの追加/削除は、単一コンポーネント内でローカルに行うことができます。コンパイラは、影響を受けるすべてのルートクエリを更新します。
- ✅ 詳細な更新 - 各コンポーネントはフラグメントを定義するため、Relayは各コンポーネントによって消費されるデータが正確にわかります。これにより、Relayは、データ変更時に最小限のコンポーネントセットが再レンダリングされる最適な更新を実行できます。
まとめ
ご覧のとおり、宣言型の構成可能なデータ取得言語(GraphQL)とコンパイラステップを組み合わせたRelayの使用により、上記で概説したすべてのトレードオフ次元で最適な結果を得ることができます。
リーフノード | ルートノード | GraphQL/Relay | |
---|---|---|---|
読み込みエクスペリエンス | 🚫 | ✅ | ✅ |
サスペンスカスケード | 🚫 | ✅ | ✅ |
構成可能性 | ✅ | 🚫 | ✅ |
詳細な更新 | ✅ | 🚫 | ✅ |