ReactでApolloClientを使ってuseQueryしてみる基本
ReactでApolloを使うキャッチアップのため、GithubのGraphQLAPIを使ってクエリを叩いてみた。 graphql-code-generatarもテストしてみたので使用感について書いてみる。
環境構築
環境構築はCRAで。
npx create-react-app graphql-react-app --template typescript
ApolloClientとGraphQLをインストールします。
yarn add @apollo/client graphql
今回は、簡単にユーザー情報を取得して表示させてみたいと思います。GitHub GraphQL Explorerでqueryをテストし、これをそのまま使用します。
ApolloProviderの読み込み
まずは、ReactアプリでApolloClientを使用するため、ApolloProviderを読み込ませます。
index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client'
const token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
const client = new ApolloClient({
uri: '<https://api.github.com/graphql>',
headers: { authorization: `Bearer ${token}` },
cache: new InMemoryCache(),
})
ReactDOM.render(
<ApolloProvider client={client}>
<React.StrictMode>
<App />
</React.StrictMode>
</ApolloProvider>,
document.getElementById('root'),
)
githubのGraphQLAPIを使用するにはtokenが必要なので、headers
に渡しています。通常、環境変数に入れるのですがここではベタ書きしています。
ComponentでuseQueryする
User.tsx
import React from 'react'
import { useQuery, gql } from '@apollo/client'
import Styled from 'styled-components'
export type User = {
user: {
name: string
url: string
location: string
avatarUrl: string
createdAt: string
}
}
const USER = gql`
query ($userName: String!) {
user(login: $userName) {
name
url
location
avatarUrl
createdAt
}
}
`
type ContainerProps = {
className?: string
}
type Props = {
data: User | undefined
} & ContainerProps
export const Component: React.VFC<Props> = ({ className, data }) => (
<div className={className}>
<div className="User__box">
<img
className="User__avatar"
src={data?.user.avatarUrl}
alt={data?.user.name}
/>
<p className="User__name">{data?.user.name}</p>
<p className="User__location">{data?.user.location}</p>
<p className="User__createdAt">{data?.user.createdAt}</p>
</div>
</div>
)
export const StyledComponent = Styled(Component)`
// css
`
export const ContainerComponent: React.VFC<ContainerProps> = (
containerProps,
) => {
const userName = 'KoheiKojima'
const { data, loading, error } = useQuery<User>(USER, {
variables: { userName },
})
const props = { data }
if (loading) return <>Loading</>
if (error) return <>{error}</>
return <StyledComponent {...containerProps} {...props} />
}
export default ContainerComponent
はい、これでApolloClientを通してGitHubのGraphQLAPIからUserデータをフェッチすることができました。
一つずつ簡単に解説していきます。
const USER = gql`
query ($userName: String!) {
user(login: $userName) {
name
url
location
avatarUrl
createdAt
}
}
`
GitHub GraphQL Explorerでテストしたクエリを記述
gql
関数でクエリ文字列をラップして、記述していきます。ここでは、userName
はベタ書きせず、引数で受け取る形式にしています。
次に、useQuery
Hookを使って上記のクエリを実行します。これは、Componentのマウント時に実行されます。
const userName = 'KoheiKojima'
const { data, loading, error } = useQuery<User>(USER, {
variables: { userName },
})
const props = { data }
if (loading) return <>Loading</>
if (error) return <>{error}</>
値としてはloading
、error
、data
が返ってきます。
Apolloクライアントは、サーバーからクエリ結果をフェッチするたびに、そのフェッチ結果をローカルに自動的にキャッシュしてくれるみたいです。 それによって、同じクエリの実行が非常に高速になるそうな。
GraphQL Code Generatorで自動的に型生成する
GraphQLのスキーマからTypeScriptの型を自動生成してくれるライブラリで、複雑になりがちな型宣言を楽ちんにしてくれる便利なツール。
基本は、公式の手順どおり進めていけば問題なかったです。
パッケージインストールする
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-react-apollo @graphql-codegen/typescript-operations
codegen.ymlの作成
yarn graphql-codegen init
質問項目は一旦すべてエンターで進め、最後のscriptコマンドは任意のコマンドを入れてください。(genarateなど)
今回は、以下のように書き換えました。
overwrite: true
schema: 'schema.graphql'
generates:
src/graphql/generate/index.ts:
documents: 'src/graphql/documents.ts'
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-react-apollo'
src/graphql/documents.ts
を参照 schema.graphql
のスキーマをTypescirptの型でsrc/graphql/generate/index.ts
に自動で生成する
schema.graphqlの作成
スキーマの記述をしていきます。 ここでは、GitHub GraphQL Explorerの型定義をほぼそのまま記述。
type User {
name: String!
url: String!
location: String!
avatarUrl: String!
createdAt: String!
}
type Query {
user(login: String!): User!
}
documents.tsの作成
src/graphql/documents.ts
では、実行したいクエリを記述します。
import { gql } from '@apollo/client'
gql`
query User($userName: String!) {
user(login: $userName) {
name
url
location
avatarUrl
createdAt
}
}
`
Componentで読み込み
yarn generate
して成功すれば、型定義ファイルが作成されます。
yarn generate
yarn run v1.22.10
$ graphql-codegen --config codegen.yml
✔ Parse configuration
✔ Generate outputs
✨ Done in 3.21s.
今回作成された型定義ファイル
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
export type Maybe<T> = T | null;
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 = {}
/** 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 Query = {
__typename?: 'Query';
user: User;
};
export type QueryUserArgs = {
login: Scalars['String'];
};
export type User = {
__typename?: 'User';
avatarUrl: Scalars['String'];
createdAt: Scalars['String'];
location: Scalars['String'];
name: Scalars['String'];
url: Scalars['String'];
};
export type UserQueryVariables = Exact<{
userName: Scalars['String'];
}>;
export type UserQuery = { __typename?: 'Query', user: { __typename?: 'User', name: string, url: string, location: string, avatarUrl: string, createdAt: string } };
export const UserDocument = gql`
query User($userName: String!) {
user(login: $userName) {
name
url
location
avatarUrl
createdAt
}
}
`;
/**
* __useUserQuery__
*
* To run a query within a React component, call `useUserQuery` and pass it any options that fit your needs.
* When your component renders, `useUserQuery` 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 } = useUserQuery({
* variables: {
* userName: // value for 'userName'
* },
* });
*/
export function useUserQuery(baseOptions: Apollo.QueryHookOptions<UserQuery, UserQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<UserQuery, UserQueryVariables>(UserDocument, options);
}
export function useUserLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<UserQuery, UserQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<UserQuery, UserQueryVariables>(UserDocument, options);
}
export type UserQueryHookResult = ReturnType<typeof useUserQuery>;
export type UserLazyQueryHookResult = ReturnType<typeof useUserLazyQuery>;
export type UserQueryResult = Apollo.QueryResult<UserQuery, UserQueryVariables>;
User
Typeなどに加えて、useUserQuery
Hookとイベントに発火させて実行させるuseUserLazyQuery
Hookも自動作成されています。これをそのままComponentで読み込ませれば、型補完がされるようになります。
const { data, loading, error } = useUserQuery({
variables: {
userName,
},
})
スキーマファイルを読み込ませれば、自動で型を作成してくれるのは良い。フロントエンド側で投げたクエリに対して、型補完が効くのはかなり利便性が高そうな印象を受けた。特に、Hooksも自動作成してくれるのはありがたい。
クエリの量が増えてきた時、自動作成がどこまで柔軟に対応できるのかが、微妙なところ。個人的にはコツコツ型定義してファイル分割して管理したほうが後々管理しやすいんじゃないかな?と感じた。 ただ、一度に型定義やHookを作成してくれるのは便利すぎる。