GraphQL の fragment による query 組み立て

4 min read読了の目安(約3700字

GraphQL では、画面上の各構成要素は、表示するために何が必要か fragment として宣言し、親が子の fragment を束ね、基本的にはルートとなるコンポーネントが query として組み立てて API 呼び出しをします。

よく語られていることではありますが、とはいえ具体例があったほうがイメージがつきやすいと思うので、Zenn の Articles ページ (https://zenn.dev/articles/explore) を題材に GraphQL fragment をどう使うかを説明します。

※想像で書いています。Zenn は実際には GraphQL を使っていなそうです。

Zenn の Articles ページを構成する要素たち

上記のページがどのような要素で作られているかを簡単に示すと、以下のようになるでしょう。

  • ArticlesPage
    • FeaturedArticles
      • ArticleCard
      • ArticleCard
      • ArticleCard
      • ...
    • TopTechArticles
      • ArticleItem
      • ArticleItem
      • ...

※ナビゲーションの表示や、ログイン状況の表示などは一旦割愛します

ここでいう「要素」とは、実際には React の component かもしれませんし、Web Components だったり、その他フレームワークを使った場合は何か別のものかもしれません。何を使っても、あるページについて画面を整理すると木構造となっているはずなので、それらの要素に名前をつけていきます。

ArticlesPage はこのページ全体を指します。
その中には、FeaturedArticles, TopTechArticles, TopIdeaArticles など、いくつかの記事の集まりを特集したものがあります。
さらに、その中には個別の記事があります。

Featured の記事たちはカード状になっています。これを ArticleCard とします。

その下の Top Tech Articles はカード状に囲うようにはなっていないようです。これを ArticleItem とします。

ArticleCard

まずは ArticleCard について、何が表示されているか書き出してみます。

この、ArticleCard を表示するために必要なデータが、そのまま fragment になります。
Zenn の GraphQL schema についての具体的な考察はここでは省略しますが、おそらく次のような fragment を ArticleCard 用に書くことになります。

fragment ArticleCard on Article {
  title
  iconUrl
  category {
    name
  }
  author {
    name
    avatarUrl
  }
  createdAt
  minutesToRead
  likes {
    totalCount
  }
}

ArticleItem

次に、ArticleItem についてです。こちらはカテゴリが不要なようですね。

同様に整理すると次のような fragment になりそうです。

fragment ArticleItem on Article {
  title
  iconUrl
  author {
    name
    avatarUrl
  }
  createdAt
  minutesToRead
  likes {
    totalCount
  }
}

FeaturedArticles

最下層の ArticleCard および ArticleItem の fragment について考えたので、次はその上の FeaturedArticles について考えます。

これは、おそらく Featured という固定文言を表示し、ArticleCard を 6 つ並べればよさそうです。

Zenn の GraphQL schema を考えると、Root Query に featuredArticles という形で問い合わせることになりそうなので、on Query な fragment になります。
このとき大事なのは、FeaturedArticles という要素が何を知っていて何を知らないか、ということです。子として ArticleCard を 6 つ並べることは知っていそうですが、その ArticleCard が具体的に何を表示しているかは知らなそうです。

fragment FeaturedArticles on Query {
  featuredArticles(first: 6) {
    ...ArticleCard
  }
}

TopTechArticles

TopTechArticles についてもほぼ同様に、Top Tech Articles という固定文言の下に 16 の記事を ArticleItem として並べればよさそうです。

fragment TopTechArticles on Query {
  topTechArticles(first: 16) {
    ...ArticleItem
  }
}

ArticlesPage

最後に、最上位の ArticlesPage です。
ここでは、一番上に Articles という固定文言とアイコン、そして FeaturedArticles や TopTechArticles 他を並べればよさそうです。
ここでもまた親が子の fragment を束ねることになります。
重要なのは、子の fragment の面倒は見るが孫の面倒は見ないということです。
なぜなら、孫としてどういう存在がいるかは親からは隠蔽されており、その孫の面倒は子が見るからです。

この要素が最上位になるため、ここでは fragment ではなく query として実際に API を叩くことになるでしょう。

query ArticlesPage {
  ...FeaturedArticles
  ...TopTechArticles
}

まとめ

GraphQL では、画面上の各構成要素は、表示するために何が必要か fragment として宣言し、親が子の fragment を束ね、基本的にはルートとなるコンポーネントが query として組み立てて API 呼び出しをします。

今回は簡単化のためにすべて Article という GraphQL Object の下でいろいろ取れることとしましたが、実際には 1 コンポーネントで複数のリソースにアクセスしたくなり、複数の fragment を持つことも多いでしょう。
そのような場合には、私は ArticleItem_Article というような「要素名 + GraphQL Object 名」という形で整理するといいんじゃないかなと考えています。

自明かもしれませんが、「fragment は各要素に紐付き整理される」ということであって、「すべての要素は fragment を持つ」ということではないことに注意してください。ArticleCard はより、汎用的な Card だったり Avatar のような汎用的な要素に分割できる可能性があります。その際、Card や Avatar は特定の GraphQL Object に紐付かないため、fragment の宣言をすることはできないですし、する必要もありません。