Edited at

reduxでgraphqlを使う

More than 1 year has passed since last update.

クライアント側はredux、redux-saga、サーバー側はnode.js(フレームワークはkoaでORMにはsequelize)という環境にgraphqlを組み合わせてみました。


利用しているgraphql周りのnpm


クライアント

react-apollo

graphql-tag

apollo-client v2

apollo-link-http

apollo-cache-redux


サーバー

apollo-server-koa

merge-graphql-schemas


apollo-client v2について

apollo-clientはv1のころはreduxとの併用ができましたが、最新のv2ではそのサポートを切っています。

そのため、apollo-client v2ではapollo-cache-reduxというものを併用する必要があります。

そのへんの話をしているissueが以下

https://github.com/apollographql/apollo-client/issues/2593

https://github.com/apollographql/apollo-client/issues/2509

graphql reduxで検索するといくつか日本語の資料も見つかるのですが、apollo-client v1の頃の実装な為、v2を利用する場合は使えないところがハマリどころでした。


コード

graphql周りの部分だけ抜粋して掲載


サーバー側の実装


server.js

const { typeDefs, resolvers } = require('./graphql');

const app = new Koa();

const server = new ApolloServer({
typeDefs,
resolvers,
});

server.applyMiddleware({ app }); // これで/graphqlというエンドポイントが用意される。



graphql/index.js

const path = require('path');

const { fileLoader, mergeTypes, mergeResolvers } = require('merge-graphql-schemas');

const typeDefs = mergeTypes(fileLoader(path.join(__dirname, './schema')));
const resolvers = mergeResolvers(fileLoader(path.join(__dirname, './resolvers')));

module.exports = {
typeDefs,
resolvers,
};



graphql/resolvers/account.js

const db = require('models');

module.exports = {
Query: {
allAccount: () => db.accounts.findAll(), // sequelizeメソッド
},
};



graphql/schema/account.js

const { gql } = require('apollo-server-koa');

const typeDefs = gql`
type Account {
id: ID!
last_name: String
first_name: String
email: String
}

type Query {
allAccount: [Account]
}
`;

module.exports = typeDefs;


schemaにはgraphqlのスキーマを定義し、resolverにはgraphqlの各クエリに対応する関数(sequelizeでデータを取ってくる関数)を紐付けるイメージです。

これで、以下のgraphqlのクエリが叩けるようになります。

  query {

allAccount {
id
last_name
first_name
email
}
}


クライアント側の実装


client.js

import { ApolloClient } from 'apollo-client';

import { ReduxCache } from 'apollo-cache-redux';
import { HttpLink } from 'apollo-link-http';
import gql from 'graphql-tag';
import store from './store'; // reduxのstore

const cache = new ReduxCache({ store });
const httpLink = new HttpLink({
uri: 'http://localhost:5000/graphql',
});

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

export default client;



reducer/index.js

import { combineReducers } from 'redux';

import ui from './ui';
import { apolloReducer } from 'apollo-cache-redux';

const reducer = combineReducers({
ui,
apollo: apolloReducer
});

export default reducer;



index.js

import { ApolloProvider } from 'react-apollo';

import client from './client';
import store from './store';
import Routes from './routes';

const history = createBrowserHistory();

const render = () => {
ReactDOM.render(
<AppContainer>
<ApolloProvider client={client}>
<Provider store={store}>
<Router history={history}>
<Routes />
</Router>
</Provider>
</ApolloProvider>
</AppContainer>,
document.getElementById('app')
);
};



account.js

import React, { Component } from 'react';

import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';

const GET_ALL_ACCOUNT = gql`
query {
allAccount {
id
last_name
first_name
email
}
}
`
;

const AccountContainerWithGql = () => (
<Query query={GET_ALL_ACCOUNT}>
{({ data, loading }) => {
const { allAccount } = data;

if (loading || !allAccount) {
return <div>Loading</div>;
}

return <AccountContainer allAccount={allAccount} />;
}}
</Query>
);

class Account extends Component {
render() {
const accounts = this.props.allAccount;

return (
// 取ってきたアカウントのデータを使ってあとは好きにコンポーネントを作成
<div>...</div>
);
}
}

const AccountContainer = connect()(Account);

export default AccountContainerWithGql;


reduxのコンテナーをreact-apolloのQueryでラップするイメージです。

react-apolloが用意してくれているloadingを使うことで、データ取得中はローディングのメッセージを出すということが簡単にできます。

また、サーバー側を見に行かずとも、クライアント側でクエリを確認、編集できるのは便利ですし、データ取得用のreduxのアクションを用意する必要もなくなるというところも便利に感じています。


apollo-clientを使うならreduxは不要?

https://qiita.com/seya/items/26c8a0dc549a10efcdf8#%E3%82%B3%E3%83%A9%E3%83%A0-graphql%E3%81%AFredux%E3%82%92%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%82%8B%E3%81%AE%E3%81%8B


コラム: GraphQLはReduxを置き換えるのか


https://www.robinwieruch.de/react-redux-apollo-client-state-management-tutorial/


Apollo Clientをテクノロジースタックに導入する際にReduxがまったく必要ないかもしれないことを指摘したいと思います。Apolloクライアントはリモートデータ用に使用されますが、Reactのローカル状態ではローカルデータを管理するのに十分です。ローカルデータの状態管理が複雑になった場合にのみ、 Reduxなどの洗練された状態管理ソリューションを導入する必要があります。


https://www.robinwieruch.de/why-apollo-advantages-disadvantages-alternatives/


アプリケーションでApollo ClientなどのGraphQLライブラリを使用することで、多くの苦労点が解消されますが、リモートデータのすべての状態管理が行われるため、ReduxやMobXなどの状態管理ライブラリをどこに置くのか混乱します。ただし、ReduxまたはMobXをローカルデータにのみ使用し、リモートデータをApolloに残すことで簡単に行うことができます。Reduxで非同期アクションを使用してデータを取得する必要はありません。したがってReduxは、残りのアプリケーション状態(ローカルデータ/ビューデータ/ UIデータなど)すべての予測可能な状態コンテナになります。おそらく、Reduxはもう必要ないかもしれませんが、


まだ少ししか書いていませんが、確かにQueryやMutationを使うとデータ取得や更新の処理は全てapollo-client側で行うことになるので、uiなどに関するstate(モーダルの開閉のstateとか)のみreduxがもつ感じになりそう?

redux sagaに至ってはdb周りの非同期処理がapollo-client側で行うことになるので全く利用しなさそうな感じがしています。