クエリの基本
このセクションでは
- ハードコードされたプレースホルダーデータを表示するReactコンポーネントを、GraphQLクエリを使ってデータをフェッチするように修正します。
- RelayがGraphQLから生成するTypeScriptの型を使用して、型安全性を確保する方法を学びます。
Relayでは、GraphQLクエリを使ってデータをフェッチします。クエリは、アプリがフェッチするGraphQLグラフの一部を、ルートノードから開始し、ノードからノードへと移動して、特定のデータセットをツリーの形で取得することを指定します。
現在、このサンプルアプリはデータをフェッチせず、Reactコンポーネントにハードコードされたプレースホルダーデータをレンダリングするだけです。Relayを使ってデータをフェッチするように修正しましょう。
Newsfeed.tsx
というファイルを開いてください。(チュートリアルのコンポーネントはすべてsrc/components
にあります。) そこには、データがハードコードされている<Newsfeed>
コンポーネントがあるはずです。
export default function Newsfeed() {
const story = {
title: "Placeholder Story",
summary:
"Placeholder data, to be replaced with data fetched via GraphQL",
poster: {
name: "Placeholder Person",
profilePicture: {
url: "/assets/cat_avatar.png",
},
},
thumbnail: {
url: "/assets/placeholder.jpeg",
},
};
return (
<div className="newsfeed">
<Story story={story} />
</div>
);
}
このプレースホルダーデータをサーバーからフェッチしたデータに置き換えます。まず、GraphQLクエリを定義する必要があります。Newsfeedコンポーネントの上に次の宣言を追加します。
import { graphql } from 'relay-runtime';
const NewsfeedQuery = graphql`
query NewsfeedQuery {
topStory {
title
summary
poster {
name
profilePicture {
url
}
}
thumbnail {
url
}
}
}
`;
これを分解してみましょう。
- JavaScript内にGraphQLを埋め込むには、
graphql``
タグでマークされた文字列リテラルを使用します。このタグにより、RelayコンパイラーはJavaScriptコードベース内のGraphQLを見つけてコンパイルできます。 - GraphQL文字列は、キーワード
query
とクエリ名を持つクエリ宣言で構成されています。クエリ名は必ずモジュール名(この場合はNewsfeed
)で始める必要があります。 - クエリ宣言内には、クエリ対象の情報を指定するフィールドがあります:
- フィールドの中には、文字列、数値、その他の情報の単位を取得するスカラーフィールドがあります。
- 他のフィールドは、グラフ内のノードから別のノードに移動できるエッジです。フィールドがエッジの場合、その後に、エッジのもう一方の端にあるノードのフィールドを含む別のブロック
{ }
が続きます。ここでは、poster
フィールドは、ストーリーからそれを投稿した人へのエッジです。Personに移動したら、その人のname
などのPersonに関するフィールドを含めることができます。
これは、このクエリが要求しているグラフの一部を示しています。
クエリを定義したので、2つのことを行う必要があります。
- Relayコンパイラーを実行して、新しいGraphQLクエリを認識させます。[npm run relay.]
- Reactコンポーネントを修正して、フェッチし、サーバーから返されたデータを使用するようにします。
package.jsonを開くと、スクリプトrelay
がRelayコンパイラーを実行するように設定されていることがわかります。これがnpm run relay
の動作です。コンパイラーが新しいコンパイル済みクエリを正常に更新/生成すると、src/components/のgeneratedフォルダーにNewsfeedQuery.graphql.tsとして見つけることができます。このプロジェクトには事前に計算されたフラグメントが付属しているため、このステップを実行しないと、目的の結果は得られません。
次に、Newsfeed
コンポーネントに戻り、プレースホルダーデータを削除することから始めます。次に、これを以下に置き換えます。
import { useLazyLoadQuery } from "react-relay";
export default function Newsfeed({}) {
const data = useLazyLoadQuery(
NewsfeedQuery,
{},
);
const story = data.topStory;
// As before:
return (
<div className="newsfeed">
<Story story={story} />
</div>
);
}
useLazyLoadQuery
フックはデータをフェッチして返します。これには2つの引数があります。
- 前に定義したGraphQLクエリ。
- クエリとともにサーバーに渡される変数。このクエリは変数を宣言しないため、空のオブジェクトです。
useLazyLoadQuery
が返すオブジェクトは、クエリと同じ形状をしています。たとえば、JSON形式で出力すると、次のようになります。
{
topStory: {
title: "Local Yak Named Yak of the Year",
summary: "The annual Yak of the Year awards ceremony ...",
poster: {
name: "Baller Bovine Board",
profilePicture: {
url: '/images/baller_bovine_board.jpg',
},
},
thumbnail: {
url: '/images/max_the_yak.jpg',
}
}
}
GraphQLクエリで選択された各フィールドが、JSONレスポンスのプロパティに対応していることに注目してください。
この時点で、サーバーからフェッチされたストーリーが表示されるはずです。
サーバーの応答は、読み込み状態を認識できるように、人為的に遅くされています。これは、アプリにインタラクティビティを追加するときに役立ちます。遅延を削除したい場合は、server/index.js
を開き、sleep()
の呼び出しを削除してください。
useLazyLoadQuery
フックは、コンポーネントが最初にレンダリングされるときにデータをフェッチします。Relayには、アプリがロードされる前にデータをプリフェッチするためのAPIもあります。これについては後で説明します。いずれにしても、RelayはSuspenseを使用して、データが利用可能になるまでロードインジケーターを表示します。
これがRelayの最も基本的な形です。コンポーネントがレンダリングされるときにGraphQLクエリの結果をフェッチします。チュートリアルが進むにつれて、Relayの機能がどのように組み合わさってアプリの保守性を高めるかを見ていきます。まず、Relayが各クエリに対応するTypeScriptの型をどのように生成するかを見ていきます。
深堀り: データローディングのためのサスペンス
サスペンスは、Reactの新しいAPIで、データを必要とするコンポーネントをレンダリングする前に、Reactがデータのロードを待機できるようにします。コンポーネントがレンダリングする前にデータをロードする必要がある場合、Reactはロードインジケーターを表示します。Suspense
という特別なコンポーネントを使用して、ロードインジケーターの場所とスタイルを制御します。
現在、App.tsx
内にSuspense
コンポーネントがあり、useLazyLoadQuery
がデータをロード中にスピナーが表示されます。
アプリにインタラクティビティを追加する際には、後のセクションでサスペンスについて詳しく見ていきます。
深掘り: クエリは静的
RelayアプリのすべてのGraphQL文字列は、Relayコンパイラーによって事前処理され、結果として得られるバンドルされたコードから削除されます。つまり、実行時にGraphQLクエリを構築することはできません。コンパイル時に認識されるように、静的な文字列リテラルである必要があります。しかし、それには大きな利点があります。
まず、Relayはクエリの結果の型定義を生成できるため、コードの型安全性が向上します。
次に、RelayはGraphQL文字列リテラルを、Relayに何をすべきかを指示するオブジェクトに置き換えます。これは、実行時にGraphQL文字列を直接使用するよりもはるかに高速です。
また、Relayのコンパイラーは、アプリをビルドするときにクエリをサーバーに保存するように構成できます。これにより、実行時にクライアントはクエリ自体ではなくクエリIDのみを送信する必要があります。これにより、バンドルサイズとネットワーク帯域幅が節約され、アプリのビルドに使用されたクエリのみが利用可能であるため、攻撃者が悪意のあるクエリを作成するのを防ぐことができます。
したがって、プログラムにGraphQLタグ付き文字列リテラルがある場合...
const MyQuery = graphql`
query MyQuery {
viewer {
name
}
}
`;
...JavaScript変数MyQuery
は、実際には次のようなオブジェクトに割り当てられます。
const MyQuery = {
kind: "query",
selections: [
{
name: "viewer",
kind: "LinkedField",
selections: [
name: "name",
kind: "ScalarField",
],
}
]
};
他のさまざまなプロパティと情報も含まれています。これらのデータ構造は、JITがRelayのペイロード処理コードを非常に高速に実行できるように慎重に設計されています。興味があれば、Relayコンパイラーエクスプローラーを使用して試すことができます。
Relayと型システム
TypeScriptがこのコードでエラーを報告することに気づくかもしれません。
const story = data.topStory;
^^^^^^^^
Property 'topStory' does not exist on type 'unknown'
これを修正するには、Relayが生成する型でuseLazyLoadQuery
の呼び出しをアノテーションする必要があります。そうすることで、TypeScriptは、クエリで選択したフィールドに基づいて、data
が持つべき型を認識します。以下を追加します。
import type {NewsfeedQuery as NewsfeedQueryType} from './__generated__/NewsfeedQuery.graphql';
function Newsfeed({}) {
const data = useLazyLoadQuery
<NewsfeedQueryType>
(NewsfeedQuery, {});
...
}
__generated__/NewsfeedQuery.graphql
の中を見ると、次の型定義が表示されます。先ほど追加したアノテーションを使用すると、TypeScriptはdata
がこの型を持つべきであることを認識します。
export type NewsfeedQuery$data = {
readonly topStory: {
readonly poster: {
readonly name: string | null;
readonly profilePicture: {
readonly url: string;
} | null;
};
readonly summary: string | null;
readonly thumbnail: {
readonly url: string;
} | null;
readonly title: string;
} | null;
};
Relayコンパイラーは、アプリ内のgraphql``
リテラルにあるすべてのGraphQLに対応するTypeScript型を生成します。npm run dev
が実行されている限り、RelayコンパイラーはJavaScriptソースファイルのいずれかを保存するたびにこれらのファイルを自動的に再生成するため、最新の状態に保つために何も更新する必要はありません。
Relayの生成された型を使用すると、アプリがより安全になり、保守しやすくなります。TypeScriptに加えて、Relayは代わりにFlow型システムを使用したい場合にもサポートします。Flowを使用する場合、useLazyLoadQuery
の追加のアノテーションは必要ありません。Flowはgraphql``
タグ付きリテラルの内容を直接理解するためです。
このチュートリアル全体を通して型を再検討します。しかし、次は、Relayが保守性を高めるのに役立つさらに重要な方法を見ていきます。
概要
クエリはGraphQLデータの取得の基礎です。私たちは見てきました。
graphql``
タグ付きリテラルを使用して、アプリ内でGraphQLクエリを定義する方法。- コンポーネントがレンダリングされたときにクエリの結果を取得するために、
useLazyLoadQuery
フックを使用する方法。 - 型安全のためにRelayで生成された型をインポートする方法。
次のセクションでは、Relayの最も核心的で特徴的な側面の一つであるフラグメントについて見ていきます。フラグメントを使用すると、各コンポーネントが独自のデータ要件を定義しながら、サーバーへの単一のクエリ発行によるパフォーマンス上の利点を維持できます。