カテゴリー
TypescriptでGraphQL Code Generatorから自動生成されるクエリ宣言から部分型を抽出する
※ 当ページには【広告/PR】を含む場合があります。
2024/09/14

GraphQLを使う際に、スキーマに従って自動で型定義を行ってくれる
スキーマの構造にも依りますが、実際に生成された型定義は、例えば、以下のようなクエリを定義して、
query GetHoge {
hoge {
piyo {
fuga {
mofu
}
}
}
}
このクエリに対応する型定義を
graphql-codegen
type GetHogeQuery = {
__typename?: 'Query';
hoge: {
__typename?: 'Hoge';
piyo: Array<{
__typename?: 'Piyo';
fuga: Array<{
__typename?: 'Fuga';
mofu: string;
}>
}>
}
}
ここから見て取れるように、
graphql-codegen
の部分型
か、その配列型
やろうと思えば、かなり深くまで同型をネストさせることができるわけですが、深くなればなるほど、どんな型が定義されているのか分からなくなってしまうのが難点です。
最近、同じような問題に取り組まれた方の記事を拝見する機会がありました。
2番煎じ感はありますが、補足説明として著者流にこの問題を扱ってみたいと思います。
自動生成したGraphQLスキーマ型の部分型を取り出す手順
少しずつ小出しに型を試して目的に近づけていく手法をとりましょう。
まず、以下のような
extractTypeName
type extractTypeName<T, __typename = ''> = T extends {[key in string]?: infer U;} ?
U : T extends (infer R)[] ?
R: never;
type GetHogeQuery = {
__typename?: 'Query';
hoge: {
__typename?: 'Hoge';
piyo: Array<{
__typename?: 'Piyo';
fuga: Array<{
__typename?: 'Fuga';
mofu: string;
}>
}>
}
};
type Q1 = extractTypeName<GetHogeQuery>;
//👇
// type Q1 = "Query" | {
// __typename?: "Hoge";
// piyo: Array<{
// __typename?: "Piyo";
// fuga: Array<{
// __typename?: "Fuga";
// mofu: string;
// }>;
// }>;
// }
type Q2 = extractTypeName<Q1>;
//👇
// type Q2 = "Hoge" | {
// __typename?: "Piyo";
// fuga: Array<{
// __typename?: "Fuga";
// mofu: string;
// }>;
// }[]
type Q3 = extractTypeName<Q2>;
//👇
// type Q3 = {
// __typename?: "Piyo";
// fuga: Array<{
// __typename?: "Fuga";
// mofu: string;
// }>;
// }
type Q4 = extractTypeName<Q3>;
//👇
// type Q4 = "Piyo" | {
// __typename?: "Fuga";
// mofu: string;
// }[]
type Q5 = extractTypeName<Q4>;
//👇
// type Q5 = {
// __typename?: "Fuga";
// mofu: string;
// }
type Q6 = extractTypeName<Q5>;
//👇
// type Q6 = string
この
extractTypeName
extractTypeName
type extractTypeName<T, __typename = ''> = T extends |
{[key in string]?: infer U;} | (infer U)[] ?
U: never;
こちらのほうが、同じ型変数を2回評価するより、断然スッキリと書けます。
次に改めて、型変数`
に文字列を指定して、
type extractTypeName<T, __typename = ''> = T extends |
{[key in string]?: infer U;} | (infer U)[] ?
U extends { __typename?: __typename }?
U: never
:never;
type Q1 = extractTypeName<GetHogeQuery,'Hoge'>;
//👇
// type Q1 = {
// __typename?: "Hoge";
// piyo: Array<{
// __typename?: "Piyo";
// fuga: Array<{
// __typename?: "Fuga";
// mofu: string;
// }>;
// }>;
// }
type Q2 = extractTypeName<Q1>;
//👇
//type Q2 = never
プロパティの名前が一致するオブジェクト型のみ抽出したかったわけですが、単純な回帰処理だと、配列型の要素のプロパティにはマッチさせられないので、手動による連鎖が続かないようになってしまいました。
ということは、
__typename
type extractTypeName<T, __typename = ''> = T extends |
{[key in string]?: infer U;} | (infer U)[] ?
U extends { __typename?: __typename }?
U: extractTypeName<U, __typename>
:never;
type Q1 = extractTypeName<GetHogeQuery,'Hoge'>;
//👇
// type Q1 = {
// __typename?: "Hoge";
// piyo: Array<{
// __typename?: "Piyo";
// fuga: Array<{
// __typename?: "Fuga";
// mofu: string;
// }>;
// }>;
// }
type Q2 = extractTypeName<GetHogeQuery, 'Piyo'>;
///👇
// type Q2 = {
// __typename?: "Piyo";
// fuga: Array<{
// __typename?: "Fuga";
// mofu: string;
// }>;
// }
type Q3 = extractTypeName<GetHogeQuery, 'Fuga'>;
//👇
// type Q3 = {
// __typename?: "Fuga";
// mofu: string;
// }
これで自動生成されたGraphQLの長ったらしいクエリの型から、確認したいメンバーの型だけをじっくりみることができるようになりました。
めでたしめでたし。
まとめ
今回は、Typescriptのテンプレートリテラル等のテクニックを駆使して、GraphQL Code Generatorから得られた複雑な型定義から、もっと見やすい型定義へ変換する方法を著者流に解説してみました。
BFFにGraphQLを使う人には開発時に便利なテクニックになるのではと考えます。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー