TL;DR
GraphQL のクエリと UI コンポーネントを一緒に置くパターン。各コンポーネントが、自分の必要とするデータを Fragment で定義する。コロケーションは「一緒に(co-)配置する(location)」という意味である。
なぜ必要か
コンポーネントの変更で必要なデータが増減しても、修正するファイルはそのコンポーネントの中に閉じるので、保守性が上がる。また、フィールドの増減に気付きやすいので、オーバーフェッチの危険も小さくなる。
仕組み
UI コンポーネントのツリーと GraphQL のツリーを相似形にすると、どのコンポーネントがどのデータを必要とするのかをうまく対応付けられる。
App Query
└ Post component └ Post fragment
└ Author component └ Author fragment
例
urql と GraphQL Code Generator を使う例を示す。まず、コンポーネントごとに Fragment を定義する。
// src/Author.tsx
import { graphql } from "./gql"
const AuthorFragment = graphql(`
fragment AuthorFragment on Author {
name
}
`)コンポーネントは、Fragment に対応するデータを Props で受け取る。GraphQL Code Generator が生成した useFragment を使うと、Fragment で取得したデータを読み出せる。
// src/Post.tsx
import { Author } from "./Author"
import { FragmentType, graphql, useFragment } from "./gql"
const PostFragment = graphql(`
fragment PostFragment on Post {
title
body
author {
...AuthorFragment
}
}
`)
export const Post = ({ postFragment }: { postFragment: FragmentType<typeof PostFragment> }) => {
const post = useFragment(PostFragment, postFragment)
return (
<>
<h2>{post.title}</h2>
<div>{post.body}</div>
<Author authorFragment={post.author} />
</>
)
}useFragment の戻り値には、Fragment に対応する型が付く。そのため、Post で publishedAt を表示しようとして {post.publishedAt} と書くと、型エラーになる。実装者は、Fragment を直して新しいフィールドを取得する必要があると気付ける。
また、GraphQL Code Generator の Fragment Masking によって、Post からは Author の詳しい情報を読めない。
自分の理解
Fragment Masking によって、コンポーネントごとの責務の分離を強制できる。