Help us understand the problem. What is going on with this article?

Apollo Client + React 入門

More than 1 year has passed since last update.

こんにちは。

この記事ではGraphQLのクライアントであるApollo Clientの入門をReactをお供に書きます。日本ではVueが一番人口多いんじゃないかという気がしているのですが、Vueが人気過ぎてReactも盛り上げねば!という謎の使命感を感じているのでReactで書くことにしました。

ただ、Apollo Clientは他のUIライブラリでも利用可能で、また考え方とかは一緒なのでReact使わないという方もぜひ読んでいってください。

話さないこと

「入門」とついてるのでちゃんとGraphQLとはなんぞや、Apollo Clientとはなんぞやという点に対して言及すべきなのですが、これについては他の記事で書いたのでそちらをご覧ください。
世のフロントエンドエンジニア達にApollo Clientを布教したい

バージョン情報

本記事内で使われている主要なパッケージのバージョンを記載します。

パッケージ名 バージョン
apollo-boost 0.1.7
graphql 0.13.2
graphql-tag 2.9.2
react 16.4.0
react-apollo 2.1.4
react-dom 16.4.0
react-scripts 1.1.4

セットアップ

create-react-app でプロジェクトを作成する

まずは手っ取り早く create-react-app でプロジェクトを作成してしまいましょう。

npx で実行するか、グローバルインストールしたい場合は以下のコマンドを実行してください。

npm install -g create-react-app

そして任意の場所にプロジェクトを作成してください。

create-react-app hello-apollo

GitHub APIセットアップ

大体GraphQLクライアントのチュートリアルはGitHub APIが利用されるので、慣習に従いこの記事でも使っていきます。もしGitHubアカウントを持っていない方がいらっしゃれば作るところから頑張りましょう。

GitHubにログインしている状態でこちらのリンクに行ってください。
https://github.com/settings/tokens

  • Generate New Tokens というボタンを押してください
  • パスワード入力後チェックボックスがズラッと出てくると思うので、適当に Token description にテキスト入力するのと、public_repo のところにチェックしてください
    • 他にチェックボックスをいくらつけても構いませんが、本チュートリアルで必要なのはpublic_repoのみです。

envファイルの設置

作成したトークンはこれから作成するアプリケーション内からGitHub APIにアクセスするために必要ですがgitで管理はしたくないので .envファイルに入れます。
先ほど作成したプロジェクト内に .env ファイルを作成してください。こんな感じ。

スクリーンショット 2018-06-03 20.43.07.png

そしてその中に以下のように記述してください。

REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN=ここに先ほど作成したトークンをコピペしてください

これによりアプリケーション内のコードからは以下のようにトークンを扱うことができるようになります。

process.env.REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN

この時ご注意いただきたいのが、create-react-app で作ったプロジェクト内でenv変数を扱う際には REACT_APP_ というプレフィックスが必要です。
それ以降は自由な名前をつけていただければと思いますが、そこだけ守りましょう。

必要なpackageのインストール

最後に、Apollo Clientを動かすために必要なパッケージをインストールします。
下記コマンドを実行してください。

npm install apollo-boost react-apollo graphql-tag graphql --save

一応各パッケージの簡易な解説を。

  • apollo-boost
    • その名の通り「とりあえずApolloで動かしたいんや!!」というあなたをブーストしてくれるパッケージです。
    • apollo-cache-inmemoryapollo-link-http などApollo Clientを使う際に基本使うことになるであろうパッケージたちが一緒にインストールされます。詳しい中身を知りたい方はREADMEをどうぞ。
  • react-apollo
    • ReactとGraphQLクエリの繋ぎこみをサポートしてくれるライブラリです。
  • graphql-tag
    • GraphQLクエリをテンプレートリテラルで書いたものをパースしてくれる便利なライブラリです。
  • graphql
    • graphql-tagのpeerDependencyとして必要です。他にも色々機能あるパッケージなんですが、今回入れる目的はそれ。

とりあえず値を取得してみる

長いセットアップが終わったところで、Apollo Clientと戯れて行きましょう!!!!
index.js をお好きなテキストエディタで開いてください。。

まずはGraphQLクライアントを作成します。

index.js
import ApolloClient from "apollo-boost";

const client = new ApolloClient({
  uri: 'https://api.github.com/graphql',
  request: operation => {
    operation.setContext({
      headers: {
        authorization: `Bearer ${
          process.env.REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN
        }`
      }
    });
  }
});

次にクエリを足します。
クエリの中身は「apollographql という組織のレポジトリ5件の "ID、名前、URL、あなたがこのレポジトリをstarしてるかどうか、レポジトリのStar数" 」を取得しています。他にどんなフィールドがあるのかはGitHub APIのドキュメントを漁ってみてください。

index.js
import gql from "graphql-tag";

const query = gql`
  {
    organization(login: "apollographql") {
      repositories(first: 5) {
        nodes {
          id
          name
          url
          viewerHasStarred
          stargazers {
            totalCount
          }
        }
      }
    }
  }
`

client
  .query({
    query
  })
  .then(result => console.log(result));

index.js の全体はこんな感じになっているはずです。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import ApolloClient from "apollo-boost";
import gql from "graphql-tag";

const client = new ApolloClient({
  uri: 'https://api.github.com/graphql',
  request: operation => {
    operation.setContext({
      headers: {
        authorization: `Bearer ${
          process.env.REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN
        }`
      }
    });
  }
});

const query = gql`
  {
    organization(login: "apollographql") {
      repositories(first: 5) {
        nodes {
          id
          name
          url
          viewerHasStarred
          stargazers {
            totalCount
          }
        }
      }
    }
  }
`

client
  .query({
    query
  })
  .then(result => console.log(result));

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
registerServiceWorker();

できたら npm run start もしくは yarn start でアプリケーションを立ち上げて、コンソールを開いてみましょう!
下記のように表示されていたら成功です。

スクリーンショット 2018-06-03 11.19.07.png

React とのつなぎこみ Query コンポーネント

次にReactコンポーネントと組み合わせてみましょう。

まずはindex.js 内の <App> をレンダーしているところを Apollo Provider を囲みましょう。これにより下位のコンポーネントのどこでも先ほど設定したGraphQLクライアントに対して参照が持てます。

index.js
import { ApolloProvider } from 'react-apollo';

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);

そしてApp コンポーネントはレポジトリのリストを表示するようにします。
App.jsを開いて以下のように書き直してみてください。

App.js
import React, { Component } from 'react';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';

const query = gql`
  {
    organization(login: "apollographql") {
      repositories(first: 5, isFork: false) {
        nodes {
          id
          name
          url
          viewerHasStarred
          stargazers {
            totalCount
          }
        }
      }
    }
  }
`;

const App = () => (
  <Query query={query}>
    {({ loading, data }) => {
      if (loading) return <p>Loading...</p>;

      const repositories = data.organization.repositories.nodes;

      return (
        <ul>
          {repositories.map(repo => (
            <li key={repo.id}>
              <a href={repo.url}>{repo.name}</a>
              <button>{repo.stargazers.totalCount} Star</button>
            </li>
          ))}
        </ul>
      );
    }}
  </Query>
);

export default App;

新しい登場人物がいますね。「Queryコンポーネント」さんです。QueryコンポーネントはRender Propパターンで実装されており(よく見る <Query render={...}>の形ではなく、childrenに関数入ってくる前提で this.props.children(queryResult) と実装していた。勉強になった。)、Queryコンポーネント内に関数を書いてあげると中身にQueryの結果が入ってきます。

それでは画面を開いて、下記のように表示されていれば成功です。

スクリーンショット 2018-06-03 11.48.39.png

CSSがないので残念な見た目ではありますが、ちゃんとGithub APIから取得したデータが表示されました。

コラム: Render Prop vs. HOC

Queryコンポーネントではなく graphql オブジェクトを react-apollo から使うことによってHOCで書くことができます。
下記が先ほどと同じことをHOCで書いた実装例です。

App.js
import React from 'react';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';

const query = gql`さっきのクエリ`;

const App = ({ data: { loading, organization } }) => {
  if (loading) return <p>Loading...</p>;

  const repositories = organization.repositories.nodes;

  return (
    <ul>
      {repositories.map(repo => (
        <li key={repo.id}>
          <a href={repo.url}>{repo.name}</a>
          <button>{repo.stargazers.totalCount} Star</button>
        </li>
      ))}
    </ul>
  );
};

export default graphql(query)(App);

個人的にはViewの部分とデータの部分がきっちり分かれていて、Apollo ClientとReactの繋ぎこみという文脈ではHOCの方が好きです。
ただ色んな状況に対応しやすいという意味ではやはりRender Propの方が上手で、例えば

  • クエリの中身をコンポーネントに渡されるpropsに応じて動的に書き換えたい
  • あるクエリが他のクエリに依存している
    • Render Propなら <Query><Query>{...}</Query></Query> とするだけで実現できる

のような例だとRender Propの方が全然少ないコードで書けるなあという印象です。
ただHOCも知ってて損はない書き方なので、頭に入れといて状況に応じて使い分けましょう。

エラーハンドリング

次にクエリを送った際のエラーハンドリングをしてみましょう。

Queryコンポーネント内で Errorハンドリングする

コンポーネント内で扱うのは非常に簡単で、error という引数をとってあげればよいだけです。

const App = () => (
  <Query query={query}>
    {({ loading, error, data }) => {
      if (loading) return <p>Loading...</p>;
      if (error) return <p>{error.toString()}</p>;

      /* renderする処理 */
    }}
  </Query>
);

クエリの中身をデタラメにすればエラー起こせるので試しにやってみましょう。

apollo-link-errorのonError で扱う

もう一つ方法があってapollo-link-erroronErrorを使ってGraphQLクライアントにエラーハンドリングさせることができます。

const client = new ApolloClient({
  uri: 'https://api.github.com/graphql',
  request: // hogehoge
  },
  onError: ({ graphQLErrors, networkError }) => {
    if (graphQLErrors) {
      // graphQLErrors固有の処理
    }
    if (networkError) {
      // networkError固有の処理
    }
  }
});

これによってコンポーネント毎に固有の処理を書く必要がなくなり、一括してエラーハンドリングの方法を指定することができます。

コラム: terminating link to non-terminatinglink

ちなみにapollo-boost抜きであれば以下のようにLinkを組み合わせて書くことになります。

const httpLink = new HttpLink({
  uri: 'https://api.github.com/graphql',
  headers: {
    authorization: `Bearer ${
      process.env.REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN
    }`
  }
});

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    // do something with graphql error
  }

  if (networkError) {
    // do something with network error
  }
});

const link = ApolloLink.from([errorLink, httpLink]);
const cache = new InMemoryCache();

const client = new ApolloClient({
  link,
  cache
});

Linkは連ねて使うことができ、Reduxのmiddlewareのようなものとイメージすると分かりやすいかもしれません。Apollo Clientにおいて重要な役割を持つ Link ですが、Apollo界ではLinkを二つに分類します。それが terminating linknon-terminating link です。

terminating linkはその名の通り最後に位置するLinkで、今回の例でいうとHttpLinkです。上記のコード例ではApolloLink.from([...])と複数のLinkの配列を作っていますが、terminating linkは必ず最後に書かなければいけません。ネットワークリクエストを生じるようなものがこの分類にされ、他にはWebSocketLinkなどがあります。

対してnon-terminating linkterminating linkに機能を足すようなものを指します。
今回の例でいうと ErrorLink ですね。

他にも色んな種類のLinkがあるので興味があればこちらをご覧ください。
Apollo: Available Links

Mutation コンポーネント

最後にデータ変更の操作 Mutation を学びましょう。
先ほどリスト表示したレポジトリたちに対してStarしたりUnstarしたりできるようにします。

まずはクエリの定義を。

参考
* https://developer.github.com/v4/mutation/addstar/
* https://developer.github.com/v4/mutation/removestar/

App.js
const ADD_STAR_REPOSITORY = gql`
  mutation($id: ID!) {
    addStar(input: { starrableId: $id }) {
      starrable {
        id
        viewerHasStarred
      }
    }
  }
`;

const REMOVE_STAR_REPOSITORY = gql`
  mutation($id: ID!) {
    removeStar(input: { starrableId: $id }) {
      starrable {
        id
        viewerHasStarred
      }
    }
  }
`;

そして Mutation コンポーネントを定義します。
例によってRender Propパターンで実装されておりますが、今回違うのはMutationのトリガーとなる関数が第一引数に入ってくることです。これをbuttonのonClickとかにセットして実行できるようにします。

App.js
import {Query, Mutation} from 'react-apollo';

const App = () => (
  <Query query={query}>
    {({ loading, data }) => {
      if (loading) return <p>Loading...</p>;

      const repositories = data.organization.repositories.nodes;

      return (
        <ul>
          {repositories.map(repo => (
            <li key={repo.id}>
              <a href={repo.url}>{repo.name}</a>
              <button>{repo.stargazers.totalCount} Stars</button>

              // 既にstarしてあるかどうかで出し分け
              {!repo.viewerHasStarred ? (
                <Mutation
                  mutation={ADD_STAR_REPOSITORY}
                  variables={{ id: repo.id }}
                >
                  {(addStar, { data, loading, error }) => (
                    <button onClick={addStar}>star</button>
                  )}
                </Mutation>
              ) : (
                <Mutation
                  mutation={REMOVE_STAR_REPOSITORY}
                  variables={{ id: repo.id }}
                >
                  {(removeStar, { data, loading, error }) => (
                    <button onClick={removeStar}>unstar</button>
                  )}
                </Mutation>
              )}
            </li>
          ))}
        </ul>
      );
    }}
  </Query>
);

こんな感じに表示されると思うので、あとは思う存分 star したり unstar したりしましょう。(せっかくなので遊び終わったあとはStarしといたままにしましょう💫)

スクリーンショット 2018-06-03 13.47.22.png

まとめ

本チュートリアルは以上です。これを機にApollo仲間が増えると嬉しいです。
本当は apollo-link-stateapollo-cache-persist などについても語りたかったのですが、さっさと記事を出したい欲が勝ってしまったので今回はベーシックなケースのみで。また記事を書くと思うのでよければそちらもお読みください。

それでは、よきコーディングを!

seya
最近の趣味はGraphQLとFigmaです。
https://note.mu/seyanote
linc-well
Linc'well(リンクウェル)は2018年創業のヘルスケアスタートアップです。我々は、医療のIT化を通じて、人々と社会の健康に貢献します。
https://www.linc-well.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした