GraphQL フラグメントでクエリをパーツ化する
フラグメントの基本
GraphQL クエリの中で、同じようなフィールドの指定(選択セット)を複数回使用する場合、それを フラグメント (Fragments) という再利用可能な選択セットとして切り出して定義しておくことができます。
例えば次の GraphQL クエリは、GitHub から自分のユーザー情報 (viewer
) と、特定のユーザーの情報 (user
) を一度に取得しています。
query QueryTwoUsers {
viewer {
login # ログインID
name # ユーザー名
url # ユーザーの GitHub ホームページ
websiteUrl # ユーザーの Web サイト
avatarUrl # ユーザーのアバター画像
}
user(login: "ログインID") {
login
name
url
websiteUrl
avatarUrl
}
}
viewer
フィールドと user
フィールドは、両方とも User 型 のフィールドで、しかも、上記の例では User
オブジェクトの中の同じフィールドを参照しています。
明らかに冗長な書き方です。
このようなケースでは、あるオブジェクトの特定のフィールドを参照するための選択セット (selection set) を、フラグメントの形で定義することができます。
次の例では、User
オブジェクトの特定のフィールドを選択するための userFragment
というフラグメントを定義し、クエリ内で参照しています。
query QueryTwoUsers {
viewer {
...userFragment
}
user(login: "ログインID") {
...userFragment
}
}
# User オブジェクトの中のフィールドを選択するためのフラグメント
fragment userFragment on User {
login # ログインID
name # ユーザー名
url # ユーザーの GitHub ホームページ
websiteUrl # ユーザーの Web サイト
avatarUrl # ユーザーのアバター画像
}
フラグメントを使用する場所では、...userFragment
のようにドットを 3 つ付けて参照します。
これを GraphQL 用語で フラグメント・スプレッド(fragment spread) といいます。
その場所にフラグメントの内容を「展開する」という意味です。
JavaScript でオブジェクトを展開するときのスプレッド構文 (...obj
) と同様のフォーマットが採用されています。
fragment
の定義は、query
の定義と同じ階層に記述することに注意してください。
また、on User
というのは、User
オブジェクトのフィールドを選択するフラグメントであることを示しており、このフラグメントは User
オブジェクトのフィールド部分でしか使えません。
上記の例では、viewer
も user
も User
型のフィールドなので、その中で問題なく ...userFragment
と参照できています。
フラグメントの使用例
上記のサンプルコードはあまり実用的じゃなかったので、もう少し実際のアプリで使いそうなクエリを載せておきます。
次の GraphQL クエリでは、ログイン中のユーザー情報と、そのユーザーの Issue 情報を取得しています。
Issue がアサインされているユーザーの情報を取得するために、userFragment
フラグメントを使っています。
query QueryLoginUser {
viewer {
...userFragment
issues(states: OPEN, last: 10) {
nodes {
id
title
assignees(last: 10) {
nodes { ...userFragment }
}
}
}
}
}
fragment userFragment on User {
login name url websiteUrl avatarUrl
}
ちなみに、GraphQL ドキュメントの中でフラグメントを定義した場合は、必ずどこかでフラグメント・スプレッドを使って参照しなければいけないというルールがあります。 これは、フラグメントの定義だけを GraphQL サーバーに送ることが無駄だからです。 例えば、GitHub の GraphQL API では、フラグメントの定義だけして参照しないと、次のようなエラーが返されます。
“Fragment userFragment was defined, but not used”
フラグメント定義内から変数を参照する
クエリ内でフラグメント・スプレッドを使用した場合、そのクエリに渡された変数 (variablse) をフラグメントの定義内からも参照できます。
次の公式サイトの例では、クエリ変数 $first
の値(デフォルト値は 3)を、フラグメントの定義内から参照しています。
フラグメント・スプレッドを記述する場所で、変数をたらい回しにしなくてよいということですね。
query HeroComparison($first: Int = 3) {
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
friendsConnection(first: $first) {
totalCount
edges {
node {
name
}
}
}
}
インライン・フラグメント (Inline fragments)
インライン・フラグメント (Inline fragments) は、インタフェース型や union 型として定義されたデータの中から、特定の型のオブジェクトのフィールドを参照するときに使用します(オブジェクト指向言語におけるダウンキャストと同じ概念です)。 インライン・フラグメントを使用するときは、クエリの選択セットの中に、次のように名前なしのフラグメントを定義するような構文で記述します。
... on 型名 {
フィールド名
フィールド名
フィールド名
}
下記は、GitHub の GraphQL API を使って、特定のリポジトリの Issue と PullRequest の一覧を取得するクエリの例です。
query QueryRecentActivities {
search(type: ISSUE, query: "repo:graphql/graphql-spec", last: 10) {
nodes {
... on Issue {
number
title
}
... on PullRequest {
title
baseRefName
}
}
}
}
検索結果は nodes
フィールドで配列として参照できるのですが、その中の各要素は SearchResultItem
型のオブジェクトになっています。
SearchResultItem
は次のような union 型として定義されており、実際に各要素がどの型のオブジェクトとして返されるかは、クエリを実行してみるまで分かりません。
"""
The results of a search.
"""
union SearchResultItem = App | Discussion | Issue | MarketplaceListing | Organization | PullRequest | Repository | User
それぞれの型のフィールドは当然異なっているので、そのまま汎用的な SearchResultItem
のフィールドとして参照することはできないようになっています。
そこで次のようにインライン・フラグメントを使うことで、そのオブジェクトが Issue
型の場合は number
と title
フィールドを参照し、PullRequest
型の場合は title
と baseRefName
フィールドを参照することを宣言します。
... on Issue {
number
title
}
... on PullRequest {
title
baseRefName
}
参照しようとしたオブジェクトが Issue
型だったときは、GraphQL サーバーは ... on PullRequest
で指定されたフィールドを返しません(その逆も同様です)。
つまり、インライン・フラグメントは、実際に返されるオブジェクトの型によって参照するフィールドを場合分けする 役割を持っています。
インライン・フラグメントはこのように union 型のフィールドを参照する場合や、インタフェースのフィールドを参照する場合に使用しますが、union 型のフィールドを参照する場合は、次のように名前付きのフラグメントを使用することもできます。
query today {
agenda {
...workout
...study
}
}
fragment workout on Workout {
name
reps
}
fragment study on StudyGroup {
name
subject
students
}
ちなみに、union 型のフィールドを要求したときに、実際にどの型のオブジェクトが返されたかは、次のように typename
メタフィールドを参照すると分かります。
これは、GraphQL のイントロスペクション機能のひとつです。
query GetIssueOrPullRequest {
repository(owner: "facebook", name: "graphql") {
issueOrPullRequest(number: 3) {
__typename
... on Issue {
closed
closedAt
}
... on PullRequest {
merged
mergedAt
}
}
}
}
Apollo Client で GraphQL のフラグメントを使用する
React を使った Web アプリでは、GraphQL ライブラリとして Apollo Client が使用されることが多いと思います。 もちろん、Apollo Client で使用する GraphQL クエリの中でもフラグメントを使用できます。
TypeScript (JavaScript) では、クエリ文字列の中から変数展開することができるので、フラグメントの定義は文字列変数(定数)の形で定義しておくと便利です。
次の例では、rateLimitFragment
という GraphQL フラグメントを、RATE_LIMIT_FRAGMENT
という文字列定数として定義しています。
import {gql} from '@apollo/client'
const RATE_LIMIT_FRAGMENT = gql`
fragment rateLimitFragment on Query {
rateLimit {
cost
remaining
}
}
`
// 自分にアサインされた PullRequest の一覧
export const QUERY_MY_PULLS = gql`
${RATE_LIMIT_FRAGMENT}
query {
...rateLimitFragment
rateLimit { cost remaining }
search(type: ISSUE, last: 100, query: "is:open is:pr review-requested:@me") {
issueCount
nodes {
... on PullRequest { number title bodyText url }
}
}
}
`
文字列定数 RATE_LIMIT_FRAGMENT
で定義されたフラグメントを使用するために、QUERY_MY_PULLS
の中で ${RATE_LIMIT_FRAGMENT}
と展開しています。
つまり、そこに fragment
の定義を記述したのと同じ振る舞いになります。
あとは適切な場所で ...rateLimitFragment
と参照すれば OK です。
このようにフラグメント定義部分を文字列定数として切り出しておけば、別の GraphQL クエリの中からも同様に参照することができます。
一応、QUERY_MY_PULLS
の使用例も。
import { FC } from 'react'
import { useQuery } from '@apollo/client'
import { PullRequestLink } from './PullRequestLink'
import { QUERY_MY_PULLS } from '../queries'
export const MyPullRequests: FC = () => {
const {loading, error, data} = useQuery(QUERY_MY_PULLS)
if (loading) return <p>Loading ...</p>
if (error) return <p>Error: {error.message}</p>
const pulls = createPullRequests(data)
return <>
<h3>自分のレビュー待ち PR</h3>
<ul>
{pulls.map(x => <PullRequestLink key={x.number} pr={x}/>)}
</ul>
</>
}
// ...
関連記事
- Apollo Client の useQuery 呼び出し部分をカスタムフックで分離する
- Apollo Client でクリック時に GraphQL クエリを実行する
- Apollo Client の Pagenation 機能を使って GraphQL API を呼び出す
- GitHub GraphQL クエリ例: マイルストーン情報を取得する (milestone)
- GitHub GraphQL クエリ例: PullRequest の情報を取得する (search)
- GitHub GraphQL クエリ例: イシュー情報を取得する (search)
- GitHub GraphQL クエリ例: リポジトリの情報を取得する (repository)