Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
16
Help us understand the problem. What is going on with this article?
@pokotyan

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は不要?

コラム: 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側で行うことになるので全く利用しなさそうな感じがしています。

16
Help us understand the problem. What is going on with this article?
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
pokotyan
admin-guild
「Webサービスの運営に必要なあらゆる知見」を共有できる場として作られた、運営者のためのコミュニティです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
16
Help us understand the problem. What is going on with this article?