1
1

【GraphQL】 Fragment Colocation とは

Posted at

はじめに

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

GraphQL の Fragment とは

GraphQL には Fragment という機能があります。
Fragment はクエリのフィールドをまとめ、再利用可能にします。

Fragment を使うと何が嬉しいのか

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

Fragment の実用例

query user {
  followers {
    id
    name
    age
  }
  followees {
    id
    name
    age
  }
}

follower フィールドと followee フィールドは同じ User 型のフィールドを参照しています。

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

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 の実用例

以下のような、ユーザー情報とそのフォロワーの情報を表示するコンポーネントがあるとします。

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>
    </>
  )
}
./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 フィールドを追加する必要がありそうです。

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>
    </>
  )
}
./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 の出番です。

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>
    </>
  )
}
./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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1