GraphQLのFragmentについての話
GraphQL における Fragment とは
GraphQL におけるFragment
とは、複数のquery
やmutation
の間で共有することができるロジックのことです。
Fragment
を単体で定義すると下記のように書けます。
fragment NameParts on Person {
firstName
lastName
}
Fragment
には関連付けされたタイプに属するフィールドが含まれます。
上記の例だと Person
型は NameParts
フラグメントが有効であるため、 firstName
フィールドと lastName
フィールドを宣言する必要があります。
そして、Person
型のオブジェクトを取得するquery
を定義するときは下記のように書くことができます。Fragment
はスプレッド構文で記載できます。
実際にはfirstName
,lastName
が展開されてフィールドにアクセス可能になっています。
query getPerson($input: InputType) {
getPerson(input: $input) {
...NameParts
avatar {
imageURL
}
}
}
Fragment
は定義するとキャッシュされ、複数のquery
やmutation
で再定義やimport
することなく使用することが可能です。
また、あるフィールドに対して返却される可能性のある型が複数存在する場合、クライアント側では下記のように記載することができます。
## この時点ではBook型とMovie型のどちらが実際に返ってくるかわからない
query getMedia($input: Input) {
getMedia(input: $input) {
... on Book {
title
page
}
... on Movie {
title
screeningTime
}
}
}
ただし、上記のquery
を正常に機能させるにはクライアントが型間のポリモーフィズムな関係を理解する必要があります。
スキーマでどのように実装する必要があるかについて、Union
とInterface
を使用する方法を見ていきます。
Union
Union
は、複数のオブジェクト型のうち 1 つを返すことができる GraphQL 型です。
Union
に含まれる型はオブジェクトである必要があります。
union Media = Book | Movie
type Query {
allMedia: [Media] # Book 型と Movie 型を持つ配列
}
上記を実際にどのように使えるか見てみましょう。
query getMedia($input: Input) {
getMedia(input: $input) {
__typename
... on Book {
title
page
}
... on Movie {
title
screeningTime
}
}
}
フィールドの戻り値にUnion
型を使うと、GraphQL
クライアントは戻り値がUnion
型のうち、どの型を返すのかわかりません。
そのため、返却される可能性のある型を全てFragment
で定義し、受け取れるようにします。
query
を実行すると下記のような結果になります。
{
"data": {
"search": [
{
"__typename": "Book",
"title": "The Complete Works of William Shakespeare",
"page": 460
},
{
"__typename": "Movie",
"title": "William Shakespeare",
"screeningTime": "02:30"
}
]
}
}
Interface
Interface は、複数のオブジェクト型に含めることができる一連のフィールドを指定します。
オブジェクトタイプがInterface
をimplements
する場合、そのInterface
のすべてのフィールドを含める必要があります。
フィールドは、そのリターンタイプとしてInterface
を持つことができます。この場合、そのInterface
をimplements
する任意のオブジェクト型を返すことができます。
下記の場合Textbook
を含めることができます。
interface Book {
title: String!
author: Author!
}
type Textbook implements Book {
title: String!
author: Author!
text: String!
}
type PhotoBook implements Book {
title: String!
author: Author!
place: String!
}
type Query {
books: [Book!] # Textbook, PhotoBook オブジェクトを持つ
}
クライアント側では、Interface
に含まれていないサブフィールドをFragment
で参照することができます。
query getBooks {
getBooks {
__typename
title
... on Textbook {
text
}
... on PhotoBook {
place
}
}
}
query
を実行すると下記のような結果が返ってきます。
{
"data": {
"books": [
{
"__typename": "Textbook",
"title": "Wheelock's Latin",
"text": "the sign of author"
},
{
"__typename": "PhotoBook",
"title": "Oops All Water",
"place": "Spain"
}
]
}
}
Union と Interface
両者は、query
発行時には同様の書き方で使うことができますが、スキーマ定義上での役割は別物だということがわかりました。
Union
は全く関係ないオブジェクトを使いたい時に使い、Interface
はある特定のフィールドセットをいろんな型で使いたいときに使うんだろうなということをふんわり理解しました。
GraphQL Code Generater で Fragment 定義を取り込みたい
@graphql-codegen/fragment-matcher
を使う必要がある。
Discussion