何をするか
TypeScript プロジェクトにおいて、Apollo Client の useQuery
フックで GraphQL クエリ呼び出しを行っていると、レスポンスの型情報が any
になってしまうことに悩むことになります。
例えば、GitHub の GraphQL クエリで、次のようにログイン中のユーザー情報を取得するとします。
useQuery
関数の戻り値の data
はデフォルトで any
型なので、そのままだと ESLint などに怒られることになります。
Unsafe array destructuring of a tuple element with an
any
value @typescript-eslint/no-unsafe-assignment
これを解消するために、useQuery
関数の型パラメーターで data
の型をセットできるのですが、
各クエリごとにこういった型情報を定義するのは面倒ですし、戻り値のオブジェクトが複雑だったりすると、型定義そのものが複雑で大変です。
そこで、このような GraphQL クエリの戻り値の型を自動生成してくれるのが Apollo CLI が提供する apollo client:codegen
コマンドです。
ここでは、GitHub の GraphQL スキーマ定義ファイルを使って、クエリレスポンスの型情報を自動生成してみます。
Apollo CLI で型情報を生成するための準備
apollo client:codegen
コマンドを利用するには、apollo
コマンド自体のインストールに加え、下記のような入力ファイルを用意してやる必要があります。
- スキーマ定義ファイル (
*.graphql
) - クエリリテラルを含む TypeScript コード(デフォルトで
gql
を検索します)
Apollo CLI のインストール
まず、Apollo CLI(apollo
コマンド)自体をインストールします。
基本的にはプロジェクトごとに devDependencies
インストールするのがよいと思いますが、型情報の生成はそこまで頻繁に行わないので、グローバルインストールでもよいかもしれません(node_modules
が肥大化するのは嫌ですし)。
GitHub の GraphQL スキーマ定義をダウンロード
Apollo CLI への入力ファイルとして、GraphQL のスキーマ定義を用意します。
GitHub の GraphQL API の場合は、下記のサイトから schema.docs.graphql
というファイルをダウンロードすれば OK です(サイズは 1MB 弱です)。
curl
コマンドや、apollo client:download-schema
コマンドを使ってエンドポイントから直接ダウンロードする方法もありますが、GitHub アクセストークンの設定が必要だったりして面倒なので、まずは上記サイトからファイルをダウンロードしてしまうのが手っ取り早いです。
ダウンロードしたファイルは、プロジェクト内のどこかに配置しておきます。
ここでは、graphql
ディレクトリに入れることにします。
クエリ文字列の含まれた TypeScript コード
Apollo Client (useQuery
) を使ったプロジェクトであれば、すでに gql
を使ったクエリ文字列の定義は TypeScript コード内に含まれていると思います。
例えば、src
ディレクトリ以下に、次のようなコードを含む .ts
ファイルが存在すれば準備 OK です。
apollo client:codegen で型情報を生成する
上記の準備が済んだら、次のようなコマンドで GraphQL クエリレスポンス用の型情報を生成できます。
言語として TypeScript を指定して、スキーマ定義としてダウンロードした schema.docs.graphql
を指定しています。
デフォルトで、src
ディレクトリ以下の *.ts
ファイルが検索されますが、--includes=foo/**/*.ts
オプションなどで調整できます。
その他のオプションは、apollo client:codegen --help
や、公式ページのヘルプ で調べられます。
型情報ファイルの生成に無事成功すると、デフォルトで入力ファイル (*.ts
) と同じディレクトリに、__generated__
というディレクトリが生成され、その中にクエリ単位で .ts
ファイルが生成されます。
今回の例では、src/userViewer.ts
内に query QueryViewer {...}
という記述があるので、このクエリ名称をもとに次のようなファイルが自動生成されます。
ドキュメンテーションコメントなども、入力したスキーマ定義ファイル (schema.docs.graphql
) から自動で適用されていていい感じです(VS Code などで編集中にこれらのドキュメントを参照できます)。
url
や avatarUrl
の型が any
になってしまっている部分の対応に関しては後述します。
あとは、次のようにこのファイルをインポートして useQuery
の型パラメーターとして使うだけです。
useViewer
では、戻り値の型として、Apollo CLI で生成したクエリレスポンス型をそのまま使っています。
実際のプロジェクトでは、アプリドメインで定義した独自型に変換した方が都合がよいかもしれません。
いずれにしても、そういった変換処理はカスタムフックのコード内に閉じて、UI コンポーネント側に染み出さないようにするのが保守性を高めるコツです。クエリに引数がある場合は、XxxVariables
という型も生成されるので、次のような感じで第2型パラメーターに指定してやります。
(応用)Prettier の設定
Prettier を使ってコードを自動フォーマット している場合は、__generated__
ディレクトリを無視設定しておきましょう。
ESLint の無視設定に関しては、自動生成されるファイル内に /* eslint-disable */
というコメントが入っているので気にしなくても大丈夫です。
(応用)npm scripts で簡単に codegen 実行できるようにしておく
apollo client:codegen
コマンドは、NPM scripts として定義して簡単に実行できるようにしておきましょう。
これで、次のように型情報を生成できるようになります。
(応用)独自のタイプが any にならないようにする
GraphQL のスキーマ定義ファイル (.graphql) の中で、次のような独自スカラー型 (custom scalar) が定義されていると、
そのような型が使われている部分の型情報がデフォルトで any
になってしまいます。
これでは意味がないので、なんとか他の TypeScript 型にマッピングしてやる必要があります。
上記のカスタムスカラー型の URI
は、少なくとも TypeScript の string
にマッピングしてやりたいところです。
これを実現するには、例えば次のようにします。
apollo client:codegen
のオプションで--passthroughCustomScalars
を指定することで、any
ではなくカスタムスカラー型のまま出力するようにする(上記の場合はURL
型で出力)。apollo client:codegen
のオプションで--customScalarsPrefix=CustomScalar
を指定することで、出力する型にプレフィックスを追加してやる(上記の場合はURL
→CustomScalarURL
型になる)。これは、既存の TypeScript 型とのコンフリクトを防ぐため。- 出力された型 (
CustomScalarURL
) に対応する型のマッピングをglobals.d.ts
などに記述する。
今回の例の場合は、カスタムスカラー型の URL
が CustomScalarURL
型として出力されることになるので、グローバルな型情報としてプロジェクトルートの globals.d.ts
に、次のような感じでマッピングを定義してやります(この例では DateTime
用のマッピングも追加しています)。
あとは、次のように型情報をジェネレートしてやります。
生成される型情報ファイルは次のように変化するので、無事 url
プロパティと avatarUrl
プロパティは CustomScalarURI
型(=string
型)として参照できるようになります。
本当はダイレクトにカスタムスカラー型の URI
を、TypeScript の string
にマッピングしたいところですけど、これはこれでまぁ分かりやすいのかもです。
(応用)Apollo の設定ファイル (apollo.config.js)
前述の実行例では、apollo client:codegen
のコマンドライン引数でスキーマ定義ファイルなどを指定していました。
プロジェクトのルートディレクトリに、apollo.config.js
というコンフィグファイルを作成しておくと、その設定を apollo client:codegen
が読み込んでくれるようになります。
複雑な設定が必要になってきたら、このファイルを作成してしまうとよいです。
ここでは、client.service.localSchemaFile
プロパティでスキーマ定義ファイルの場所を設定しているので、コマンド実行時のオプション指定を次のように省略できます。
apollo.config.js
の詳しい設定方法は下記の公式ドキュメントを参照してください。
トラブルシューティング
Apollo does not support anonymous operations
apollo client:codegen
コマンドを実行したときに、次のようなエラーになったら、
TypeScript コードの中で gql
を使って定義したクエリに名前が付いていないものがあります。
次のように、名前を付けてやれば解決します。
(↑のコードを↓のように変更する)
これで、うまく実行できるようになります。
関連記事
- Apollo Client の useQuery 呼び出し部分をカスタムフックで分離する
- Apollo Client でクリック時に GraphQL クエリを実行する
- Apollo Client の Pagenation 機能を使って GraphQL API を呼び出す
- Apollo Client で GitHub GraphQL API を使う (Node & React)
- GraphQL クエリ仕様: フラグメント (Fragments) とインラインフラグメント (Inline Fragments)
- GitHub GraphQL クエリ例: マイルストーン情報を取得する (milestone)
- GitHub GraphQL クエリ例: PullRequest の情報を取得する (search)