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 によって、コンポーネントごとの責務の分離を強制できる。

関連

出典