2021年にGraphQL Code Generatorを使うなら、TypedDocumentNodeを使おう

この記事は [GraphQL Advent Calendar 2020](https://qiita.com/advent-calendar/2020/graphql) 8日目の記事です。
前回の記事は @mtsmfm さんの [GraphQL アンチパターン - 孫煩悩 -](https://qiita.com/mtsmfm/items/efa6c023bfc3d4c4df9e) でした。

## 概要

`TypedDocumentNode` は、 [GraphQL Code Generator](https://graphql-code-generator.com/) を使って TypeScript のコードを生成するときの出力先のひとつで、 2020年7月に登場しました。

[GitHub](https://github.com/dotansimha/graphql-typed-document-node) 
[GraphQL Code Generator 公式ドキュメント](https://graphql-code-generator.com/docs/plugins/typed-document-node)

出力結果はただの `DocumentNode` オブジェクト（`gql` 関数にクエリを渡して得られるもの）ですが、 TypeScript のジェネリクスを使って型が定義されているのが特徴です。

[リリース記事](https://the-guild.dev/blog/typed-document-node) には以下のように書かれています。

> 2016: Manual Typings
> 2017: Generated Types
> 2018: Generated Code
> 2020: NEW TypedDocumentNode

2016年、初めは手動で型定義をしていたものが、 2017年に GraphQL Code Generator が登場し、 QueryResult や Variables の型を自動生成するようになりました。
しかしこの段階では自動生成された型定義をクエリの呼び出し時に手動で渡す必要があり、実際に実行されるクエリと型定義が一致している保障はありませんでした。

その後2018年、 型定義を既に渡してあるコードを自動生成するようになりました。特に React の場合は Hooks を生成するようになってからグッと使用感がよくなりました。

2020年に登場した `TypedDocumentNode` はこれらに変わる新しいソリューションというようにうたっています。

## 使い方

こちらも [リリース記事](https://the-guild.dev/blog/typed-document-node) の内容を引用して React Hooks の例を挙げると、従来の Hooks の生成では

```tsx
import { useRatesQuery } from './generated';

const result = useRatesQuery({ variables: {...} });
```

と生成された Hook を呼び出していたところを、 `TypedDocumentNode` を使う場合は

```tsx
import { useQuery } from '@apollo/client';
import { ratesQuery } from './generated';

const result = useQuery(ratesQuery, { variables: {...} });
```

と生成された `TypedDocumentNode` を `@apollo/client` の `useQuery` に渡します。

[Supported Libraries](https://github.com/dotansimha/graphql-typed-document-node#supported-libraries) を見ると React の場合 `@apollo/client` 、 Vue の場合 `villus` がビルトインで対応しています。

その他のライブラリでは [`patch-package`](https://github.com/ds300/patch-package) を使うことで対応できると書いてありますが、パッチを当てると見通しが悪くなってしまうので、例えば [`graphql-request`](https://github.com/prisma-labs/graphql-request) で使うときは以下のように薄いラッパー関数を書いています。

```ts
export async function gqlFetch<TData = any, TVariables = Record<string, any>>(
  operation: TypedDocumentNode<TData, TVariables>,
  variables?: TVariables
) {
  return client.request<TData, TVariables>(operation, variables);
}
```


## メリット

### 冗長なコードが少なくなる

従来のコード生成では、型をあてるために個別にラッパー関数やコンポーネントのコードを生成しており、実行に影響するレベルではありませんが冗長な関数が必ず1つ以上挟まる形になっていました。
型推論が強化されたことによりこれらが不要になり、結果として生成されるコードの行数も少なくなります。

### さまざまな場面で使える

React Hooks を生成している場合、生成された Hooks を使うだけであれば非常にシンプルに書けるのですが `fetchMore`, `subscribeToMore`, `refetchQueries` のように Hooks 以外で `DocumentNode` を使う必要がある場合は型の恩恵を受けようとするとそこだけ複雑な書き方をする必要がありました。

これらの場面でも `TypedDocumentNode` を使っているだけで型推論されます。

### ライブラリの仕様変更に強い

`TypedDocumentNode` は単なる型が付与されたオブジェクトなので、生成されたコードはクライアントライブラリに直接依存していません。

`graphql-js` に破壊的変更が入らない限りクライアントライブラリ側の変更の影響を受けないということなので、  GraphQL Code Generator とクライアントライブラリのバージョンアップはお互いを意識することなくできるようになります。

~~というかむしろ `graphql-js` v16 から標準機能として組み込まれるようです。~~
~~[GitHub Issue](https://github.com/graphql/graphql-js/issues/2727)~~
[twitterでコメントをいただきました](https://twitter.com/Quramy/status/1336466727023075336) が、 v15.4 ですでに `TypedQueryDocumentNode` が追加済みでした。


これはライブラリの開発者にとってみても開発しやすい作りとなっています。

## デメリット

### Hooks を生成したほうが自分で書くコードは短くなる

サンプルコードを見ると `TypedDocumentNode` を使ったほうが生成された Hooks を使うよりも自分で書くコードの行数としては多くなってしまいます。
ただしコード補完を使ったりすれば大したデメリットにはならないでしょう。

## まとめ

ここまで書いて[以前書いたブログ記事](https://blog.sinki.cc/entry/2020/08/18/190426)とほぼかぶっていることに気づきましたが、これはリリース時から使っていて特に困ったこともなくネガティブなことがないためだと思っています。

まだ登場して日が浅いためサンプルコードも少ないですが、 TypeScript で GraphQL を使う際の主流になるのは間違いなさそうです。（またすぐに他の書き方が発明されるかもしれませんが...）
従来の書き方ともあまり変わりませんし設計的にも信頼できる作りとなっていますので、後から書き換えるようであれば初めから `TypedDocumentNode` を採用してみてはいかがでしょうか。