Kohei Blog

夫・父親・医療系エンジニア

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をテストし、これをそのまま使用します。

GitHub GraphQL Explorer

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はベタ書きせず、引数で受け取る形式にしています。

次に、useQueryHookを使って上記のクエリを実行します。これは、Componentのマウント時に実行されます。

  const userName = 'KoheiKojima'

  const { data, loading, error } = useQuery<User>(USER, {
    variables: { userName },
  })
  const props = { data }

  if (loading) return <>Loading</>

  if (error) return <>{error}</>

値としてはloadingerrordataが返ってきます。

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>;

UserTypeなどに加えて、useUserQueryHookとイベントに発火させて実行させるuseUserLazyQueryHookも自動作成されています。これをそのままComponentで読み込ませれば、型補完がされるようになります。

  const { data, loading, error } = useUserQuery({
    variables: {
      userName,
    },
  })

スキーマファイルを読み込ませれば、自動で型を作成してくれるのは良い。フロントエンド側で投げたクエリに対して、型補完が効くのはかなり利便性が高そうな印象を受けた。特に、Hooksも自動作成してくれるのはありがたい。

クエリの量が増えてきた時、自動作成がどこまで柔軟に対応できるのかが、微妙なところ。個人的にはコツコツ型定義してファイル分割して管理したほうが後々管理しやすいんじゃないかな?と感じた。 ただ、一度に型定義やHookを作成してくれるのは便利すぎる。