GraphQLのTypeScript / React の型の自動生成
TypeScript x Reactの自動生成に関しては、Guide: React and GraphQL に一通りガイドがある
ここでは、主に出力される結果とその使用例を記す。
フロントとして必要な型定義、またあると便利な型定義は下記三つ。
- スキーマ(SDL)ファイルからTypeScriptの型の自動生成
- フロントの定義による、リクエストとレスポンスなどの型の自動生成
- Apollo ClientやReact Query用のHooksの自動生成
下記の ./schema.graphql
ファイルが、ディレクトリのルートに存在するとする
type Author {
id: Int!
firstName: String!
lastName: String!
posts(findTitle: String): [Post]
}
type Post {
id: Int!
title: String!
author: Author
}
type Query {
posts: [Post]
}
1. スキーマ(SDL)ファイルからTypeScriptの型の自動生成
codegen.yml には最低限の内容。
schemaの指定と出力先の指定のみ。
// ./codegen.yml
schema: ./schema.graphql
generates:
./src/models.ts:
plugins:
- typescript
./src/models.ts
に、下記のように ./schema.graphql
に定義されているスキーマの全種類の型が出力される。
// ./src/models.ts
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
};
export type Author = {
__typename?: 'Author';
firstName: Scalars['String'];
id: Scalars['Int'];
lastName: Scalars['String'];
posts?: Maybe<Array<Maybe<Post>>>;
};
export type AuthorPostsArgs = {
findTitle?: InputMaybe<Scalars['String']>;
};
export type Post = {
__typename?: 'Post';
author?: Maybe<Author>;
id: Scalars['Int'];
title: Scalars['String'];
};
export type Query = {
__typename?: 'Query';
posts?: Maybe<Array<Maybe<Post>>>;
};
2.フロントの定義による、リクエストとレスポンスなどの型の自動生成
下記の ./queries/posts.ts
のように、ディレクトリルートの ./queries/
ディレクトリにqueryファイルが存在するとする。(フロント側による定義)
// ./queries/posts.ts
import {gql} from '@apollo/client';
export const PostsDocument = gql`
query Posts {
posts {
id
title
author {
id
firstName
lastName
}
}
}
`;
codegen.yml のdocumentsに上記の queries
ディレクトリを指定する
// ./codegen.yml
schema: ./schema.graphql
documents: ./queries
generates:
./src/models.ts:
- typescript
- typescript-operations
./src/models.ts
に上述の型定義に合わせて、下記のPostsQuery が出力される
// ./src/models.ts
export type PostsQueryVariables = Exact<{ [key: string]: never; }>;
export type PostsQuery = { __typename?: 'Query', posts?: Array<{ __typename?: 'Post', id: number, title: string, author?: { __typename?: 'Author', id: number, firstName: string, lastName: string } | null } | null> | null };
使用例: 自動生成された型をレスポンスの型に指定し使用する
import {useQuery} from '@apollo/client';
import {PostsQuery} from "./src/models.ts";
import {PostsDocument} from "./src/queries/posts.ts"
const {loading, data, error} = useQuery<PostsQuery>(PostsDocument);
3.Apollo ClientやReact Query用のHooksの自動生成
2番のようにGenericsを指定するのではなく、リクエストやレスポンスの型が定義されているhooksを使用する場合。
codegen.yml にapolloのpluginを追加
// ./codegen.yml
schema: ./schema.graphql
documents: ./queries
generates:
./src/models.ts:
- typescript
- typescript-operations
- typescript-react-apollo
上述の型定義に合わせて、下記の型定義も出力される
// ./src/models.ts
export const PostsDocument = gql`
query Posts {
posts {
id
title
author {
id
firstName
lastName
}
}
}
`;
/**
* __usePostsQuery__
*
* To run a query within a React component, call `usePostsQuery` and pass it any options that fit your needs.
* When your component renders, `usePostsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = usePostsQuery({
* variables: {
* },
* });
*/
export function usePostsQuery(baseOptions?: Apollo.QueryHookOptions<PostsQuery, PostsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<PostsQuery, PostsQueryVariables>(PostsDocument, options);
}
export function usePostsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PostsQuery, PostsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<PostsQuery, PostsQueryVariables>(PostsDocument, options);
}
export type PostsQueryHookResult = ReturnType<typeof usePostsQuery>;
export type PostsLazyQueryHookResult = ReturnType<typeof usePostsLazyQuery>;
export type PostsQueryResult = Apollo.QueryResult<PostsQuery, PostsQueryVariables>;
下記のように usePostsQuery
として各種型が指定されているhooksを使用可能となる。
import {usePostsQuery} from "./src/models.ts";
const {loading, data, error} = usePostsQuery()
typescript-react-apollo
を typescript-react-query
のプラグインに置き換えれば、React Queryも同様に出力される
// ./codegen.yml
schema: ./schema.graphql
documents: ./queries
generates:
./src/models.ts:
- typescript
- typescript-operations
- typescript-react-query
@graphql-codegen/typed-document-node
Apollo Client対応だと、よりよいプラグインがあると紹介されている。
useQueryをそのまま使用させつつ、型推論をさせる。
low bundle impact (compared to other plugins)
better backward compatibility (easier to migrate to)
more flexible
プラグインは typed-document-node
に変更。
// ./codegen.yml
schema: ./schema.graphql
documents: ./queries
generates:
./src/models.ts:
- typescript
- typescript-operations
- typed-document-node
今回新たに自動出力された内容
// ./src/models.ts
export type PostsQueryVariables = Exact<{ [key: string]: never; }>;
export type PostsQuery = { __typename?: 'Query', posts?: Array<{ __typename?: 'Post', id: number, title: string, author?: { __typename?: 'Author', id: number, firstName: string, lastName: string } | null } | null> | null };
export const PostsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Posts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"posts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}}]}}]}}]} as unknown as DocumentNode<PostsQuery, PostsQueryVariables>;
Apollo ClientのuseQueryはそのまま使用。
自動出力されたDocumentの定義によって、型推論させる。
import {useQuery} from '@apollo/client';
import {PostsDocument} from "./src/models.ts";
const {loading, data, error} = useQuery(PostsDocument)