TypeScript + Apollo Clientで、useQueryなどを用いてGraphQLのクエリを発行する際に、クエリのvariablesやレスポンスのデータに型を付けたい。やり方が少々分かりづらかったのでメモを残す。
型をつけるためにやることは以下の通り。
- apollo.config.jsを定義する
- graphql-tagでGraphQLのクエリを書く
- apollo client:codegenで型情報を生成する
- クエリを書いた場所で型をimportし、useQueryの型パラメータに指定する
shibayu36/go-Intern-Diaryのレポジトリで試している。
apollo.config.jsを定義する
まずApolloのコードジェネレータが適切に動作するために、apollo.config.jsを作る。Configuring Apollo projects - Apollo Basics - Apollo GraphQL Docsを参考にする。
今回の場合、GraphQLのスキーマはGraphQLエンドポイントから取得し、クライアントから発行するクエリは./ui/src/
以下のファイルに定義するので、次の内容になる。
module.exports = { client: { includes: ['./ui/src/**/*.tsx', './ui/src/**/*.ts'], service: { name: "go-Intern-Diary", url: "http://localhost:8000/query" } } };
graphql-tagでGraphQLのクエリを書く
続いてクライアント側でgraphql-tagを使ってクエリを書く。クエリに付けた名前が型情報の名称に使われるため、必ずクエリにプロジェクトでユニークな名前を付けておく。
import gql from "graphql-tag"; ... const getDiaryQuery = gql` query GetDiary($diaryId: ID!) { diary(diaryId: $diaryId) { id name } } `
apollo client:codegenで型情報を生成する
apollo.config.jsを用意し、graphql-tagでクエリを書いていれば、apollo client:codegenコマンド一つで型情報を生成できる。useReadonlyTypesオプションを使うと全てreadonlyで型情報が作成されるため、意図しない代入を防げる。好みに合わせて--outputFlatも使うと良さそう。
npx apollo client:codegen --useReadOnlyTypes --target=typescript
これでui/src/pages/__generated__/GetDiary.tsのように、クエリ名がファイル名となり型情報が出力される。
クエリを書いた場所で型をimportし、useQueryの型パラメータに指定する
あとは上で出力された型情報をimportし、useQueryの型パラメータに指定する。
import { GetDiary, GetDiaryVariables } from "./__generated__/GetDiary"; ... const { loading, error, data } = useQuery<GetDiary, GetDiaryVariables>( getDiaryQuery, { variables: { diaryId: diaryId } } );
これでdata
変数はGetDiary型となり、変なプロパティにアクセスするとコンパイルエラーが発生するようになる。またvariablesもdiaryId以外のプロパティを指定するとコンパイルエラーになる。間違った使い方にすぐに気付けるようになって安心。
ちなみに、useQueryはloadingやerrorが返ってくる時dataはundefinedとなるため、data.diary
とかするとObject is possibly 'undefined'
のようにコンパイルエラーになってしまう。先にloadingやerrorのチェックをしていれば、dataは存在するとみなせるはずなので、!.
演算子を使うと良さそう(参考: tslint - In Typescript, what is the ! (exclamation mark / bang) operator when dereferencing a member? - Stack Overflow)。
if (loading) return <p>Loading...</p>; if (error) return <p>{error.message}</p>; return <div className="Diary"> <h1>{data!.diary.name}</h1> </div>;
参考: variablesが存在しない時のやり方
クエリにvariablesが存在しない時はuseQueryの型パラメータの一番目のみ指定すれば良い。
const { loading, error, data } = useQuery<GetMyDiaries>(getMyDiariesQuery);
まとめ
useQueryの型の付け方や、client:codegenの使い方がドキュメントからだとよく分からずに結構ハマってしまった。今回で理解できたので、ようやくGraphQLのクエリをバリバリ書けそう。