この記事は GraphQL Advent Calendar 2020 8日目の記事です。
前回の記事は @mtsmfm さんの GraphQL アンチパターン - 孫煩悩 - でした。
概要
TypedDocumentNode
は、 GraphQL Code Generator を使って TypeScript のコードを生成するときの出力先のひとつで、 2020年7月に登場しました。
GitHub
GraphQL Code Generator 公式ドキュメント
出力結果はただの DocumentNode
オブジェクト(gql
関数にクエリを渡して得られるもの)ですが、 TypeScript のジェネリクスを使って型が定義されているのが特徴です。
リリース記事 には以下のように書かれています。
2016: Manual Typings
2017: Generated Types
2018: Generated Code
2020: NEW TypedDocumentNode
2016年、初めは手動で型定義をしていたものが、 2017年に GraphQL Code Generator が登場し、 QueryResult や Variables の型を自動生成するようになりました。
しかしこの段階では自動生成された型定義をクエリの呼び出し時に手動で渡す必要があり、実際に実行されるクエリと型定義が一致している保障はありませんでした。
その後2018年、 型定義を既に渡してあるコードを自動生成するようになりました。特に React の場合は Hooks を生成するようになってからグッと使用感がよくなりました。
2020年に登場した TypedDocumentNode
はこれらに変わる新しいソリューションというようにうたっています。
使い方
こちらも リリース記事 の内容を引用して React Hooks の例を挙げると、従来の Hooks の生成では
import { useRatesQuery } from './generated';
const result = useRatesQuery({ variables: {...} });
と生成された Hook を呼び出していたところを、 TypedDocumentNode
を使う場合は
import { useQuery } from '@apollo/client';
import { ratesQuery } from './generated';
const result = useQuery(ratesQuery, { variables: {...} });
と生成された TypedDocumentNode
を @apollo/client
の useQuery
に渡します。
Supported Libraries を見ると React の場合 @apollo/client
、 Vue の場合 villus
がビルトインで対応しています。
その他のライブラリでは patch-package
を使うことで対応できると書いてありますが、パッチを当てると見通しが悪くなってしまうので、例えば graphql-request
で使うときは以下のように薄いラッパー関数を書いています。
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
twitterでコメントをいただきました が、 v15.4 ですでに TypedQueryDocumentNode
が追加済みでした。
これはライブラリの開発者にとってみても開発しやすい作りとなっています。
デメリット
Hooks を生成したほうが自分で書くコードは短くなる
サンプルコードを見ると TypedDocumentNode
を使ったほうが生成された Hooks を使うよりも自分で書くコードの行数としては多くなってしまいます。
ただしコード補完を使ったりすれば大したデメリットにはならないでしょう。
まとめ
ここまで書いて以前書いたブログ記事とほぼかぶっていることに気づきましたが、これはリリース時から使っていて特に困ったこともなくネガティブなことがないためだと思っています。
まだ登場して日が浅いためサンプルコードも少ないですが、 TypeScript で GraphQL を使う際の主流になるのは間違いなさそうです。(またすぐに他の書き方が発明されるかもしれませんが...)
従来の書き方ともあまり変わりませんし設計的にも信頼できる作りとなっていますので、後から書き換えるようであれば初めから TypedDocumentNode
を採用してみてはいかがでしょうか。