【GraphQL】 Fragment Colocation とは

## はじめに
GraphQLを使う中で、Fragment Colocationという手法があることを知りました。
実際に使ってみて Fragment Colocation を利用することでの利点が見えてきたので、記事にまとめようと思います。

## GraphQL の Fragment とは
GraphQL には [Fragment](https://graphql.org/learn/queries/#fragments) という機能があります。
Fragment はクエリのフィールドをまとめ、再利用可能にします。

### Fragment を使うと何が嬉しいのか
* 再利用が可能になることで、繰り返し記述することを避けられる
  * 繰り返し処理を Fragment としてまとめることで見通しが良くなる
* 仮にフィールド名が変更になったときに複数箇所変更する必要がなくなる

### Fragment の実用例
```graphql
query user {
  followers {
    id
    name
    age
  }
  followees {
    id
    name
    age
  }
}
```
follower フィールドと followee フィールドは同じ User 型のフィールドを参照しています。

id, name, age が重複しており、冗長になっているため、Fragment を使うことで重複を解消します。

```graphql
query users {
  followers {
    ...userFragment
  }
  followees {
    ...userFragment
  }
}
fragment userFragment on User {
  id
  name
  age
}
```

「...フラグメント名」（上の例でいうと `...userFragment`）とすることで Fragment が利用できます。

## Fragment Colocation とは
Fragment について理解できたところで、「じゃあ Fragment Colocation とはなんなんだ」という話をしていきます。

Colocate とは、「同じ場所に配置する」という意味です。
Fragment Colocation とは、fragmentを用いてクエリを分割し、データと UI Component を同じ場所に配置する手法です。

### Fragment Colocation の実用例

以下のような、ユーザー情報とそのフォロワーの情報を表示するコンポーネントがあるとします。
```react:UserPage.tsx
import { gql, useQuery } from '@apollo/client';

const USER_QUERY = gql`
  query userQuery {
    user {
      name
      followers {
        id
        name
        age
      }
    }
  }
`;

export const UserPage: React.FC = () => {
  const { data } = useQuery(USER_QUERY);

  if(!data?.user) return <div>Not Found<div/>;

  return (
    <>
      <div>
        <p>Your Name: {data.user.name}</p>
      </div>
      <div>
        <div>followers</div>
        <ul>
          {data.user.followers.map((follower) => (
            <li key={follower.id}>
              <Follower follower={follower} />
            </li>
          ))}
        </ul>
      </div>
    </>
  )
}
```

```react:./Follower.tsx
export type Props = {
  readonly follower: {
    name
    age
  }
}

export const Follower: React.FC<Props> = ({ follower }) => {
  return (
    <>
      <div>name: {follower.name}</div>
      <div>age: {follower.age}</div>
    </>
  )
}
```

フォロワーの名前と年齢に加え、ニックネームも表示したい、という要件が出てきたとします。
そのためには、UserPage.tsx に置いているクエリに nickname フィールドを追加する必要がありそうです。

```react:UserPage.tsx
import { gql, useQuery } from '@apollo/client';

const USER_QUERY = gql`
  query userQuery {
    user {
      name
      followers {
        id
        name
        age
        nickname
      }
    }
  }
`;

export const UserPage: React.FC = () => {
  const { data } = useQuery(USER_QUERY);

  if(!data?.user) return <div>Not Found<div/>;

  return (
    <>
      <div>
        <p>Your Name: {data.user.name}</p>
      </div>
      <div>
        <div>followers</div>
        <ul>
          {data.user.followers.map((follower) => (
            <li key={follower.id}>
              <Follower follower={follower} />
            </li>
          ))}
        </ul>
      </div>
    </>
  )
}
```

```react:./Follower.tsx
export type Props = {
  readonly follower: {
    name
    age
    nickname
  }
}

export const Follower: React.FC<Props> = ({ follower }) => {
  return (
    <>
      <div>name: {follower.name}</div>
      <div>age: {follower.age}</div>
      <div>nickname: {follower.nickname}</div>
    </>
  )
}
```

本来、 UI の修正としては Follower コンポーネントを修正したいはずなのに、その親の UserPage.tsx にクエリを置いていることで、UserPage.tsx の修正も合わせて必要になっています。

ここで Fragment Colocation の出番です。

```react:UserPage.tsx
import { gql, useQuery } from '@apollo/client';
import { FOLLOWER_FIELDS } from './Follower';

const USER_QUERY = gql`
  query userQuery {
    user {
      name
      followers {
        id
        ...followerFields
      }
    }
  }
  ${FOLLOWER_FIELDS}
`;

export const UserPage: React.FC = () => {
  const { data } = useQuery(USER_QUERY);

  if(!data?.user) return <div>Not Found<div/>;

  return (
    <>
      <div>
        <p>Your Name: {data.user.name}</p>
      </div>
      <div>
        <div>followers</div>
        <ul>
          {data.user.followers.map((follower) => (
            <li key={follower.id}>
              <Follower follower={follower} />
            </li>
          ))}
        </ul>
      </div>
    </>
  )
}
```

```react:./Follower.tsx
import { gql } from '@apollo/client';

export const FOLLOWER_FIELDS = gql`
  fragment followerFields on User {
    name
    age
    nickname
  }
`

export type Props = {
  readonly follower: {
    name
    age
    nickname
  }
}

export const Follower: React.FC<Props> = ({ follower }) => {
  return (
    <>
      <div>name: {follower.name}</div>
      <div>age: {follower.age}</div>
      <div>nickname: {follower.nickname}</div>
    </>
  )
}
```

Fragment として name, age, nickname フィールドを Follower.tsx に置くことで、変更が本来修正したいファイルのみで完結します。

### Fragment Colocation を使わない場合、他にどんな困りごとがありそうか

追加の場合はクエリを変更しないと情報が取れないため、「追加のし忘れ」のようなことは起こらないと思います。

しかし、逆にいらなくなったフィールドが出てきたときを考えてみると、UI コンポーネントとデータフェッチの場所が離れていた（Fragment Colocation を使わずに全て親のコンポーネントにクエリを置いていた）場合、気づくことができるでしょうか？

「クエリからフィールドを削除しなければいけないかもしれない」と気付いたとして、親コンポーネントがたくさんの子コンポーネントを持っていた場合、他のコンポーネントも確認した上で本当に削除して良いフィールドなのかを判断する必要が出てきそうです。


上記の例だとかなり単純な構造なので、あるフィールドが不要になった場合の修正も用意ではありますが、実務だともっと複雑な構造であることが多いと思います。

そんな時に Fragment Colocation を使い、データと UI コンポーネントを同じ位置に置くことで、「どこでどの情報を使っているのか？」がわかりやすくなり、管理もしやすくなります。

## さいごに
Fragment Colocation を理解したことで、コンポーネントの保守性を高めることができるようになりました。
実務でもどんどん Fragment Colocation を活用していきたいと思います。

## 参考
https://graphql.org/learn/queries/#fragments
https://zenn.dev/so_nishimura/articles/2c8796761f2d02