こんにちは。
この記事では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
ファイルを作成してください。こんな感じ。
そしてその中に以下のように記述してください。
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-inmemory
やapollo-link-http
などApollo Clientを使う際に基本使うことになるであろうパッケージたちが一緒にインストールされます。詳しい中身を知りたい方はREADMEをどうぞ。
-
react-apollo
- ReactとGraphQLクエリの繋ぎこみをサポートしてくれるライブラリです。
-
graphql-tag
- GraphQLクエリをテンプレートリテラルで書いたものをパースしてくれる便利なライブラリです。
-
graphql
-
graphql-tag
のpeerDependencyとして必要です。他にも色々機能あるパッケージなんですが、今回入れる目的はそれ。
-
とりあえず値を取得してみる
長いセットアップが終わったところで、Apollo Clientと戯れて行きましょう!!!!
index.js
をお好きなテキストエディタで開いてください。。
まずはGraphQLクライアントを作成します。
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のドキュメントを漁ってみてください。
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
の全体はこんな感じになっているはずです。
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
でアプリケーションを立ち上げて、コンソールを開いてみましょう!
下記のように表示されていたら成功です。
React とのつなぎこみ Query コンポーネント
次にReactコンポーネントと組み合わせてみましょう。
まずはindex.js
内の <App>
をレンダーしているところを Apollo Provider
を囲みましょう。これにより下位のコンポーネントのどこでも先ほど設定したGraphQLクライアントに対して参照が持てます。
import { ApolloProvider } from 'react-apollo';
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);
そしてApp
コンポーネントはレポジトリのリストを表示するようにします。
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の結果が入ってきます。
それでは画面を開いて、下記のように表示されていれば成功です。
CSSがないので残念な見た目ではありますが、ちゃんとGithub APIから取得したデータが表示されました。
コラム: Render Prop vs. HOC
Queryコンポーネントではなく graphql
オブジェクトを react-apollo
から使うことによってHOCで書くことができます。
下記が先ほどと同じことをHOCで書いた実装例です。
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なら
のような例だと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-error
のonError
を使って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 link
と non-terminating link
です。
terminating link
はその名の通り最後に位置するLinkで、今回の例でいうとHttpLink
です。上記のコード例ではApolloLink.from([...])
と複数のLinkの配列を作っていますが、terminating linkは必ず最後に書かなければいけません。ネットワークリクエストを生じるようなものがこの分類にされ、他にはWebSocketLink
などがあります。
対してnon-terminating link
はterminating link
に機能を足すようなものを指します。
今回の例でいうと ErrorLink
ですね。
他にも色んな種類のLinkがあるので興味があればこちらをご覧ください。
Apollo: Available Links
Mutation コンポーネント
最後にデータ変更の操作 Mutation
を学びましょう。
先ほどリスト表示したレポジトリたちに対してStarしたりUnstarしたりできるようにします。
まずはクエリの定義を。
参考
- https://developer.github.com/v4/mutation/addstar/
- https://developer.github.com/v4/mutation/removestar/
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とかにセットして実行できるようにします。
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しといたままにしましょう💫)
まとめ
本チュートリアルは以上です。これを機にApollo仲間が増えると嬉しいです。
本当は apollo-link-state
や apollo-cache-persist
などについても語りたかったのですが、さっさと記事を出したい欲が勝ってしまったので今回はベーシックなケースのみで。また記事を書くと思うのでよければそちらもお読みください。
それでは、よきコーディングを!