LoginSignup
20
17

More than 5 years have passed since last update.

reduxでgraphqlを使う

Last updated at Posted at 2018-08-26

クライアント側は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は不要?

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

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

アプリケーションで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側で行うことになるので全く利用しなさそうな感じがしています。

20
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
17