LoginSignup
1
0

More than 1 year has passed since last update.

ReactにおけるFragment Colocationとはなにか

Last updated at Posted at 2022-09-08

始めに

Reactを使ったGraphQLのFragment Colocationに関する記事です。
GraphQL APIを利用するフロントエンド開発では、Fragment Colocationは非常に便利です。しかしながら、初めてGraphQLを利用したときは、Fragmentの重要性に気づきませんでした。そこで参考になればと思い、以前身内で発表したものを少し肉付けして、記事にしました。(なお私もそんなにわかっているわけではないので悪しからず)

主に以下ついて述べます。

  • GraphQLのFragmentとは
  • Fragment Colocationとは
  • Fragment Colocationは何を変えるか
  • Typescriptとの併用について

Reactの利用を前提として記事を書いていますが、コンポーネント指向なフロントエンドであれば、主旨は変わらないと思います。

Fragment is 何

Fragmentは、GraphQLを構成する単位の一つであり、重複するfieldsをまとめる機能があります。以下はspec.graphqlにのっている例です。

まずはFragmentを使わない例です。あるuserのfriendsmutualFriendsを取得しています。しかし、queryを見るとわかりますが、friendsmutualFriendsの中のfieldは全く同じです。

query noFragments {
  user(id: 1) {
    friends {
      id
      name
      profilePic
    }
    mutualFriends {
      id
      name
      profilePic
    }
  }
}

次に以下がFragmentを利用して重複を排除した例です。

query withFragments {
  user(id: 4) {
    friends {
      ...friendFields
    }
    mutualFriends {
      ...friendFields
    }
  }
}

fragment friendFields on User {
  id
  name
  profilePic
}

これは最初の例と全く同じqueryを表現していますが、friendFields Fragmentを利用して重複していたfieldの共通化が可能になりました。
Fragmentはスプレッド演算子によって使われることに注意しましょう。また、on Userは既に定義してあるオブジェクトの型です。Fragmentはon句でオブジェクトを指定して、そのオブジェクトが持つfieldを定義することができます。なので、Userオブジェクトは以下のように定義されているはずです。

type User {
  id
  name
  profilePic
  ... # その他のfield
}

上記のように、Fragmentを用いると重複するfieldを共通化してDRYなqueryを書くことができました。
これだけだと、ただのFieldの共通化でそれほど魅力的な機能に見えないかもしれません。しかし、Reactのコンポーネントと組み合わせることでより強力に使いこなすことができます。

Fragment Colocation

Fragment ColocationはApolloのDocsによると

A colocated fragment is just like any other fragment, except it is attached to a particular component that uses the fragment's fields.

意訳すると
「colocated fragmentとは、特定のコンポーネントで利用するFragmentをそのコンポーネントに付け加えることだ」
とあります。
といってもわかりにくいので例を上げて説明します。

例えば、先程のfriendを用いたページを考えてみましょう。以下にFragmentを利用していないqueryを用意します。

query noFragments {
  friend {
    id
    name
    profilePic
  }
}

一方、queryを利用する側のコンポーネントを擬似的に以下のように書きます。

index.jsx
const Index = () => {
  const friend  = execute(query) //queryを利用してAPIから取得
  return (
    <>
      <Hoge />
      <Fuga />
      <Friend friend={friend} />
    </>
  )
}
Friend.jsx
export const Friend = (friend) => {
  return (
    <>
      <p>friend.id</p>
      <img>friend.profilePic</img>
      <p>friend.name</p>
    </>
  )
}

上記をFragment Colocationの手法で書き換えます。
やり方は、Friendコンポーネントで利用するFragmentを用意し、そのFragmentを利用するFriendコンポーネントに配置するだけです。
まずFragmentを利用するようにquery書き換えます。

query withFragments {
  friend {
    ...friendFiedls
  }
}

続いて、そのFragmentをFriendコンポーネントと同じ場所に書きます。

Friend.jsx
export const Friend_Feilds = gql`
fragment friendFields on User {
  id
  name
  profilePic
}
`
export const Friend = (friend) => {
  return (
    <>
      <p>friend.id</p>
      <img>friend.profilePic</img>
      <p>friend.name</p>
    </>
  )
}

これで終わりです。あっけないですね。一体何が変わったのでしょうか。

Fragment Colocationで何が変わったか

まずnoFragments queryを利用した場合を考えます。このとき noFragments queryはFriendコンポーネントで使われるプロパティを知っている必要があります。そのため、Friendコンポーネントで使うプロパティに性別(gender)が追加された場合、queryの定義にgenderを追加する必要があります。

一方、withFragmentsを使った場合はどうでしょう。性別のプロパティの追加によって変更するのはfriendFields Fragmentです。
大元のwithFragments queryはFriendコンポーネントで必要なプロパティを知っている必要がありません。

queryが知っているのは、friendFields FragmentがFriendコンポーネントに配置(colocated)されるFragmentであることだけです。

Friendコンポーネントで使われるプロパティ(具象)に依存していたのが、friendFeildsという抽象に依存するようになったのです。

次にFragmentをFriendコンポーネントと同じ場所に配置したことで、取得するデータを利用するコンポーネントと同じ場所で定義することができました。取得するデータを決めるのはそのデータを使うコンポーネントであるという責務を明確にし、凝集度(?)が高くなり見通しがよくなります。

実はコンポーネントごとのFragmentは使うが、コンポーネントと同じファイルに置くのは嫌だという流派の人もいます。(私です)

ここまででFragment Colocationの説明は終わりです。ここからは、FragmentはTypescriptとも相性が良いという話をします。

Typescriptと型生成

GraphQLを利用する多くのプロジェクトはTypescriptを利用しており、GraphQLのスキーマから自動で型を生成していると思います。
ここで面倒なことがあります。GraphQLのqueryの戻り値型(これは自動生成)からあるコンポーネントの引数となる型のみを作ることです。
例えば大元のページにFrinedコンポーネントとArticleコンポーネントがあるとしましょう。この2つのコンポーネントで利用するデータは以下のように1つのQueryで取得することができます。

query user {
  user(id: 1) {
    friends {
      id
      name
    }
    articles {
      id
      title 
    }
  }
}

それぞれのコンポーネントでは違うデータが必要なので、それぞれ引数の型を作る必要があります。自動生成されたqueryの戻り値型からFriend用とArticle用の型を抜き出すのは意外と面倒です。(この程度なら難しくはないですが、実際はもっと複雑です)
いちいち自分で作るのも面倒だという人のために多くの型生成ライブラリはFragmentの型も自動で作ってくれます。
それぞれのコンポーネントで使うデータをFragmentとして定義しておけば、引数の型として必要なものも自動で出来上がります。
これは非常に便利です。

以上、Fragment Colocationの説明でした。最近フロント触ってないのでもっと便利なものがあるかもしれません。

1
0
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
0