今回やること
前回はフロント側からgraphqlクエリを叩いてScore
の一覧を表示しましたが、Score
の型はこちらで手動で作りました。
前回のコード↓
...
type Score = {
id: Number
title: String
}
...
これをgraphql-code-generatorでコマンド一発で型定義を作成できるようにします。
ライブラリのインストール
まずライブラリをインストールします。
$ yarn add -D @graphql-codegen/cli
他にも必要なプラグインをインストール
$ yarn add --dev @graphql-codegen/typescript @graphql-codegen/typescript-graphql-request @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
セットアップ
以下のコマンドを実行すると設定ファイルを対話式で作ってくれます。(設定ファイルを自分で作ってもよいです)
$ npx graphql-codegen init
色々聞かれるので答えていきます。
私が回答した内容は以下です
? Where is your schema?: (path or url) http://localhost:3000/graphql
? Where are your operations and fragments?: ./graphql/documents/**/*.graphql
? Pick plugins: TypeScript (required by other typescript plugins), TypeScript Operations (operations and fragments), TypeScript React Apollo (typed
components and HOCs)
? Where to write the output: ./graphql/generated.tsx
? Do you want to generate an introspection file? No
? How to name the config file? codegen.yml
? What script in package.json should run the codegen? codegen
一応大事そうなところを説明すると、
Where is your schema?
サーバーサイドのgraphqlのエンドポイントを書いてます。このエンドポイントからschemaを読み取って型定義などしてるっぽいです。
Where are your operations and fragments?
フロントで使うqueryやmutationなどを書くファイル(これから作ります)のパスを作ります。今回はgraphql
というフォルダを作って、queryは/graphql/query/〇〇.graphql
、mutationは/graphql/mutation/〇〇.graphql
に書こうと思っているので、このようにしています。
Where to write the output?
コマンドを打った時にここに型定義が出力されます。
How to name the config file?
What script in package.json should run the codegen?
package.json
に設定に応じたコマンドが追加されます。
できた設定ファイルがこちら
overwrite: true
schema: "http://localhost:3000/graphql"
documents: "./graphql/documents/**/*.graphql"
generates:
./graphql/generated.tsx:
plugins:
- "typescript"
- "typescript-operations"
- "typescript-react-apollo"
package.json
にもコマンドが追加されています。
...
"private": true,
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start",
+ "lint": "next lint",
+ "codegen": "graphql-codegen --config codegen.yml"
},
"dependencies": {
...
コマンド実行
以上で設定は完了しました!
コマンドを実行する前にフロントで使用するqueryを書いていきます。
前回使ったScoreの一覧を取得するqueryを書いていきます。
query Scores {
scores {
id
title
}
}
これでコマンドを実行します。
yarn以下はpackage.json
にさきほど追記されたコマンドですね
$ yarn codegen
無事、以下のように型定義が作成されました!
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
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]> };
const defaultOptions = {} as const;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
ISO8601DateTime: any;
};
export type Mutation = {
__typename?: 'Mutation';
/** An example field added by the generator */
testField: Scalars['String'];
};
export type Query = {
__typename?: 'Query';
score: Score;
scores: Array<Score>;
};
export type QueryScoreArgs = {
id?: InputMaybe<Scalars['Int']>;
};
export type Score = {
__typename?: 'Score';
createdAt: Scalars['ISO8601DateTime'];
id: Scalars['ID'];
title?: Maybe<Scalars['String']>;
updatedAt: Scalars['ISO8601DateTime'];
};
export type ScoresQueryVariables = Exact<{ [key: string]: never; }>;
export type ScoresQuery = { __typename?: 'Query', scores: Array<{ __typename?: 'Score', id: string, title?: string | null }> };
export const ScoresDocument = gql`
query Scores {
scores {
id
title
}
}
`;
/**
* __useScoresQuery__
*
* To run a query within a React component, call `useScoresQuery` and pass it any options that fit your needs.
* When your component renders, `useScoresQuery` 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 } = useScoresQuery({
* variables: {
* },
* });
*/
export function useScoresQuery(baseOptions?: Apollo.QueryHookOptions<ScoresQuery, ScoresQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ScoresQuery, ScoresQueryVariables>(ScoresDocument, options);
}
export function useScoresLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ScoresQuery, ScoresQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ScoresQuery, ScoresQueryVariables>(ScoresDocument, options);
}
export type ScoresQueryHookResult = ReturnType<typeof useScoresQuery>;
export type ScoresLazyQueryHookResult = ReturnType<typeof useScoresLazyQuery>;
export type ScoresQueryResult = Apollo.QueryResult<ScoresQuery, ScoresQueryVariables>;
作成された型定義を使用して書き換え
実際に生成された型定義を使って、前回作ったindex.tsx
を書き換えてみたのが以下です。
import { Box } from '@chakra-ui/react'
import type { ReactElement } from 'react'
import { Layout } from '../components/common/Layout'
import type { NextPageWithLayout } from './_app'
import { useQuery } from "@apollo/client";
import { Score, ScoresDocument } from '../graphql/generated';
const Page: NextPageWithLayout = () => {
const { data, loading, error } = useQuery(ScoresDocument)
if (loading) return <Box>ロード中...</Box>;
if (error) return <Box>{error.message}</Box>;
return (
<Box>
<ul>
{data.scores.map((score: Score) => (
<li>{score.title}</li>
))}
</ul>
</Box>
)
}
Page.getLayout = function getLayout(page: ReactElement) {
return (
<Layout>
{page}
</Layout>
)
}
export default Page
queryもScoresDocment
という形で書き換えられ、前回自力で作成したScore
の型定義もサーバーサイドのschemaを使用して生成されたものなので、型づけの安全性が高まっています!
今回はここまで!
参考にさせていただいたサイト・記事
graphql-code-generator
初めての GraphQL。Code Generator で型を生成するまでのメモ