全集中 Reactの呼吸 Relayの舞 『戯れ』
はじめに
こちらは『DMMグループ '20卒内定者 Advent Calendar 2019'』21日目の記事になります。
本記事は投稿主がNext(React)でGraphqlを使いたいが「apollo多いよね。relay使ってみたくない?」というお気持ちでreact-relayを用いた スターターキットを作り戯れる話 です。
- はじめに
- GraphqlとRelayについて
- Apolloはよく聞くけど、Relayはあまり聞かないよね。
- スターターキットを作る
- 感想
- 参考リポジトリ
- その他
GraphqlとRelayについて
Graphql
TOPかっこいいですね。非常にクールです。
GraphqlはRESTの課題解決をしようぜというお気持ちから生まれた規格です。 解決したい課題としてはいくつかありますが、投稿主が使っていて感じている利点として 必要なリソースを1つのリクエストで得られるという点にあるのかなと。
おかげさまで幾度もフェッチせずに済み、必要なデータもコンポーネントごとにfragment(Graphqlクエリの断片化)を定義することでDX最高です。
Relay
Graphqlのクールさとは相まって、ポップなTOPが良いですね。
Relayは単一のGraphqlリクエストで結果を得られるようにしたり、Mutation(データの作成・更新)も簡易的に実行できるようサポートされているGraphqlクライアントです。ありがたい。
もちろん、Graphqlクライアントということで以下のようなメリットがあります。
- コレクションのページネーション
- データキャッシュ
- Mutation後にキャッシュの一貫性保持
また、Relayには制限もあります。
- 採用技術がReact or ReactNativeになる。
- 導入するアプリケーション構造の自由度を下げる。
- Relay Server Specificationの規約でクエリとレスポンスの冗長性が少し増す。
Apolloはよく聞くけど、Relayはあまり聞かないよね。
日本記事の検索("react graphql")
アポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロ。
海外記事の検索("how to use graphql with react")
ようやく”Relay”の単語が出てきましたね!ちなみに、この記事の内容はApolloでした。
GraphqlクライアントにApolloが多い理由
単純な理由がいくつかあります。
- Apolloは様々な環境で使用できるが、RelayはReactがメインである。
- Apolloは軽量で柔軟なアプローチができるが、Relayは自由度が低い。
- Apolloはとにかく資料が豊富でキャッチアップが早いが、Relayの資料は少ない。
Apolloはこれから学習するので詳細には語れませんが、細かい比較内容をそのうち記事化します。
現在ではVue.jsでもRelayを導入する個人制作のパッケージ(vue-relay)などがありますが、やはり公式としてはReactオンリーのようです。
スターターキットを作る
0. 前提
フロントエンド構成は以下を利用する。
- TypeScript: ^3.7
- Next: ^9.1
- React
- nodenv
- react-relay
- relay-runtime
- relay-compiler
- @graphql-codegen
- @material-ui など
1. フロントエンド構成の説明
Next.js
今回、フロントエンドのフレームワークはZeit社のNext.jsを使用していきます。NextはReactを包含してSSRを可能とする素敵な技術ですが、仕事先のプロジェクトでがっつり使っていくということで今回はReact単体ではなくNextも含めて採用しました。
TypeScipt(3.7系)
Graphqlの型システムを利用するにはこれしかないでしょ!ということでいつものTypeScriptです。3.7系からOptional Chainingが利用できるという点も含めて最新を採用します。
nodenv
言わずもがな、nodenv。 プロジェクトごとにnodeバージョンを切り替えます。
react-relay, relay-runtime, relay-compiler
ReactでRelayを使用するコアパッケージや、Graphqlのスキーマファイルから各コンポーネントに対応する型定義ファイルを自動生成してくれる方々です。
@graphql-codegen
GraphqlスキーマファイルからTSの型定義ファイルを生成することに利用します。便利。
@material-ui
毎度お馴染み、Material Designのパッケージです。
2. Next + TSの環境作成
ざっとこんな感じの構成を作りました。 尚、Nextはpagesディレクトリなど、Next規定のディレクトリやファイルを作成すればちゃんと認識してくれます。
次セクションでディレクトリやファイルをピックアップして解説します。
project/ ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── _error.tsx │ └── index.tsx ├── public │ ├── fonts │ ├── icons │ └── images ├── src │ ├── components │ ├── layouts │ ├── lib │ ├── styles │ └── types │ └── tsconfig.json
next.config.js
Nextの設定ファイルになります。Nuxtでいうnuxt.configのようなものになります。
- Dotenv
環境を振り分けるケースがわりかし多かったので入れました。
注意点としては通常のDotenvではなくdotenv-webpack
を使用することになる点です。
- withSass
今回はsrc/styles
にスタイリングファイル(Sass)を分離し、CSS moduleとして各コンポーネントで利用したかったので使用しました。
const withSass = require('@zeit/next-sass'); const Dotenv = require('dotenv-webpack'); const path = require('path'); module.exports = withSass({ cssModules: true, sassLoaderOptions: { includePaths: ["src/styles/**/*.sass"] }, webpack: config => { config.plugins = config.plugins || []; config.plugins = [ ...config.plugins, new Dotenv({ path: path.join(__dirname, '.env'), systemvars: true }) ]; return config } });
pages/
Nextがページとして認識するディレクトリです。 各ページファイルだけでなく、Next規定のファイル(_hogehoge.tsx)も設置します。
public/
アセットファイルを設置するディレクトリです。 以前はstaticでしたが、最近のバージョンではpublicに名を変えています。
src/components
コンポーネントを格納します。 Graphqlのfragment(断片化されたクエリ)を定義したコンポーネントなども含めるため、relay-compilerに読み取らせると各fragmentコンポーネントに必要な型定義が生成されます。 こんな感じ。
src/layouts
ページのレイアウトファイルを格納しています。 headerやfooterをセットにしたファイルですね。
src/lib
material-uiのテーマファイルやRelayのコンフィグ、ユーティリティなどを格納します。
src/styles
CSS moduleとして使用するSassファイルを格納します。 ここの細かい構造はまた今度。
3. Graphqlスキーマの生成
Graphqlのエンドポイントからスキーマファイルを生成するためget-graphql-schemaをインストールします。
使用
インストール後、Graphqlエンドポイントへ向けてスキーマファイルを生成するためのコマンドを実行します。
> get-graphql-schema <endpoint_url> schema.graphql
するとsrc/types/schema.graphql
が1秒もかからずにファイルが生成され、以下のようになります。
type User implements Node { id: ID! user: User! } """The connection type for History.""" type UserConnection { """A list of edges.""" edges: [UserEdge] """Information to aid in pagination.""" pageInfo: PageInfo! totalCount: Int! } ... // スキーマが続く。すごいぞ。
4. コードジェネレーターの導入
Graphqlスキーマファイルから型定義ファイルを自動生成するため@graphql-codegenを導入します。 ひとまず、この辺りのパッケージを入れておきました。
"@graphql-codegen/cli": "^1.9.1", // 必須 "@graphql-codegen/typescript": "^1.9.1", //必須 "@graphql-codegen/typescript-operations": "^1.9.1", "@graphql-codegen/typescript-resolvers": "^1.9.1",
また、自動生成にあたってのyamlを定義しなければならないのですが、最初は特にこだわることもないかなと思いますので以下のようにシンプルな定義にします。
overwrite: true schema: "schema.graphql" generates: src/types/graphql.ts: plugins: - typescript - typescript-resolvers
使用
諸々が整った段階でコマンドを実行します。いざ、ジェネレート。
> graphql-codegen --config codegen.yml
するとスキーマからsrc/types/graphql.ts
が生成され、スキーマの型定義ファイルとして使用できるようになります。このような感じ。openAPIと同様ですね。
import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; export type Maybe<T> = T | null; export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; export type RequireFields<T, K extends keyof T> = { [X in Exclude<keyof T, K>]?: T[X] } & { [P in K]-?: NonNullable<T[P]> }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { ID: string, String: string, Boolean: boolean, Int: number, Float: number, /** An ISO 8601-encoded datetime */ ISO8601DateTime: any, }; ... //型定義が続く。強いぞジェネレータ。
5. RelayのEnvironmentファイル作成
クライアント-Graphqlエンドポイント間での通信やレスポンスをキャッシュするためのストアなど、Relayをフルに使っていくために定義をする必要があります。
import {Environment, Network, RecordSource, Store} from 'relay-runtime'; export const fetchQuery = (operation: any, variables: any) => { return fetch(process.env.RELAY_ENDPOINT as string, { method: 'POST', body: JSON.stringify({ query: operation.text, variables, }), headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, }).then((response: Response) => { if (response.ok) { return Promise.reject(response.statusText); } return response.json() }); }; const environment = new Environment({ network: Network.create(fetchQuery), store: new Store(new RecordSource()) }); export default environment;
ここで重要なのが以下の2つ。
NetWork
- QueryRendererコンポーネントからGraphqlのクエリや変数を受け取り通信を行う。
Store
- レスポンスを格納しキャッシュ、ローカルステートとして利用できるようにしてくれる。
6. Envファイル作成と完成
最後に、エンドポイントは様々なのでenvファイルで対応しておきましょう。
RELAY_ENDPOINT="http://localhost:xxxx/graphql"
以上でキットが完成しました。 そして、このキットを用いることでGraphql + Reactの開発を開始することができます。やったね。
感想
環境構築から既にしんどい思いをしたので、プロジェクトの立ち上がりがそこそこ遅いです。ですので、やるならばしっかりとスターターキットを作るべきだと感じました。
問題は土台のメンテナンスコストが負担になる点でしょうか。 環境構築の時点で非常に多くのパッケージを入れたかと思います。
また、Apolloも使ってみてどちらを採用するのか検討していきたいと思いますが、その前にせっかくRelayのキットを作ったので、 次回はキットを用いてコンポーネントを実装していく記事を書いていきたいと思います。
ではまた。
参考リポジトリ
本日のスターターはこちら。 まだ作っただけですので、READMEやexampleなど整備していきます。
その他
Githubが公式でGraphqlのお試しを用意しているので、触ってみたい方はぜひ。 Docsをクリックすると叩けるクエリなどが表示されます。(これ、なんと自分らのクライアントでもできてしまうんです。)
あ、クエリの叩き方はご自分で勉強してくださいね。