[React] Ducks というデザインパターン [Redux]

[React] Ducks というデザインパターン [Redux]

Ducks というデザインパターン

Redux Way

React + Redux で構築する際のディレクトリ構成としてよく知られるのは、redux-way と呼ばれるやつです。Reduxの登場人物であるところの、container, reducer, action(action creator) それぞれについてディレクトリを分けてファイル単位で分割します。ディレクトリ構成はこんな感じになります。

src
├─── actions/
├─── constants/
├─── containers/
└─── reducers/

メリットはきっちりと役割単位で分けられること。デメリットは分離されすぎてどこに何があるかわかりずらいこと。

Ducks

それに対して Ducks というデザインパターンは、action(action creator), reducer, constantsmodule というファイルにまとめてしまいます。

どのみち密結合になっているので1ファイルにまとめたほうが見通しが良く管理しやすいという理由です。

こうすることですっきりと次のようなディレクトリ構成となります。

src
├─── containers/
└─── modules/

ではこのディレクトリ構成(Ducks)でサンプルアプリ(カウンター)を作ってみます。

環境構築

Reactの開発環境は create-react-app で作成しますので、まずはこれをインストールします。

$ npm install -g create-react-app

プロジェクトを作成します。プロジェクト名は適当です。

$ create-react-app redux-ducks
$ cd ./redux-ducks

必要なツールをインストールします。

$ npm install --save redux react-redux redux-logger

module

ducks における module とは、Action, ActionCreateor, Reducer をひとまとめにしたものです。これらは別ファイルに分けてもどうせ密結合しがちだから、1ファイルにまとめて module として定義しようという考え方です。

  • export default では、Reducer を定義する。
  • exportActionCreator を関数として定義する。
  • action は定数として定義する。

ひとまず上記の感じで実装します。いずれもピュアな実装です。

modules/Counter.js

// action type
const INCREMENT = 'COUNTER_INCREMENT';
const DECREMENT = 'COUNTER_DECREMENT';

const initialState = {
  state: 0,
}

// reducer
export default function reducer(state=initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + 1
      }

    case DECREMENT:
      return {
        ...state,
        count: state.count - 1
      }

    default:
      return state;
  }
}

// action-creator
export function increment() {
  return { type: INCREMENT };
}

export function decrement() {
  return { type: DECREMENT };
}

createStore

ストアを生成するための処理を定義します。Rducerは module に定義されています。それを使って createStore 関数を定義します。

import { createStore as reduxCreateStore, applyMiddleware, combineReducers } from 'redux';
import logger from 'redux-logger';
import counterReducer from './modules/Counter';

export default function createStore() {
  const store = reduxCreateStore(
    combineReducers({
      counter: counterReducer,
    }),
    applyMiddleware(
      logger,
    )
  );

  return store;
}

コンポーネント

コンポーネントを定義します。コンテナからマッピングされた dispatch と state を使います。

import React from 'react';

export default class Counter extends React.Component {
  render() {
    const { increment, decrement, counter } = this.props;
    return(
      <div>
        <h2>カウンター</h2>
        <button onClick={increment}>increment</button>
        <button onClick={decrement}>decrement</button>
        <div>
          Count: {counter.count}
        </div>
      </div>
    )
  }
}

Container の定義

コンテナを定義します。dispatch に必要な ActionCreator は module で定義されているのでそれを使います。

import { connect } from 'react-redux';
import * as counterModule from '../modules/Counter';
import Counter from '../components/Counter';


const mapStateToProps = state => {
  return {
    counter: state.counter,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    increment: () => dispatch(counterModule.increment()),
    decrement: () => dispatch(counterModule.decrement()),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Redux を組み込む

最後に Redux を組み込んで、コンポーネントを表示させましょう。

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

import { Provider } from 'react-redux';
import configureStore from './configureStore';

const store = configureStore();

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>, 
  document.getElementById('root')
);
registerServiceWorker();

App.js

import React, { Component } from 'react';
import './App.css';
import Counter from './containers/Counter';

class App extends Component {
  render() {
    return (
      <div className="App">
        <Counter />
      </div>
    );
  }
}

export default App;

雑感

ファイル構成がすっきりしていい感じになったように思います。以降このスタイルを使っていきたいと感じました。

以上。

参考URL

Javascriptカテゴリの最新記事