最近のReact/Redux周り晒す

[Nextremer Advent Calendar 2017](https://qiita.com/advent-calendar/2017/nextremer)の２日目。

一年ほどReact/Redux使って開発してきたので、ここらで一度現在のReact/Redux環境周りを晒してみる

# 歴史
### 2016/4〜
流行りにのって、React/Reduxを採用
この頃はMiddlewareにはredux-thunkを利用

### 2016/6〜
激しくスパゲティ化してしまい、thunkでの運用に限界を感じたため、thunkの利用を諦め、redux-sagaに鞍替え
この頃から複数のプロジェクトでReact/Reduxの運用を開始

### 2017/6〜
Reduxの通常構成（Action, Reducer, Saga）の冗長さに辟易していたので、[Ducksパターン](https://qiita.com/uryyyyyyy/items/a88f37b76fe434c62bac)の採用を検討。
その際、[Moducks](https://github.com/moducks/moducks)という素晴らしいライブラリを発見したため、これを採用した。
同時に、Reducerから取得した値をViewで加工するのにもうんざりしていたため、[reselect](https://github.com/reactjs/reselect)も合わせて採用。
いまここ

# 最新のReact/Redux周りの構成
## 使用ライブラリ
### フレームワーク
- [React](https://facebook.github.io/react/)
- [Redux](https://github.com/reactjs/redux)
- [Redux-Saga](https://github.com/redux-saga/redux-saga)
- [Moducks](https://github.com/moducks/moducks)
- [Reselect](https://github.com/reactjs/reselect)
- [CSS Modules](https://github.com/gajus/react-css-modules)

### ビルド
- [Webpack](https://webpack.js.org/)
- [Babel](https://babeljs.io/)
- [PostCSS](https://github.com/postcss/postcss)

### テスト
- [ESLint](https://eslint.org/)
- [StyleLint](https://stylelint.io/)
- [Jest](https://facebook.github.io/jest/)

## ディレクトリ構成
```
|-- _tests__ # jest
|-- src
|  |-- css
|  `-- js
|     |-- components
|     |  `-- # React Components
|     |-- config
|     |  `-- # Configuration Files
|     |-- containers
|     |  `-- # React-Redux Containers
|     |-- modules
|     |  `-- # Moducks Modules
|     |-- selectors
|     |  `-- # Reselect Selectors
|     |-- utils
|     |  `-- # Utility Files
|     |-- constants.js
|     |-- index.js
|     |-- reducers.js
|     |-- routes.js
|     |-- sagas.js
|     `-- store.js
|-- package.json
`-- webpack.config.js
```

## アーキテクチャ
さっくりと。基本的にRedux周りはすべてModucksに閉じ込める。Moducks->Viewには適宜Selectorをかますようにしている。
<img width="821" alt="スクリーンショット 2017-08-22 16.24.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/16629/9d841186-e8ef-f87e-35a4-060f9b7d8ac4.png">

## ビルド
GruntやGulpは使わずに、NPM ScriptでWebapckを走らせる構成。
WebpackでBabelやらPostCSSやらを実行する。

FuseBoxとか使ってみたい...

# 実装例
この構成で実装するならこんな感じというのをざっと書いてみる
例えばToDo作るならこんな感じかな（動作未検証）。

## modules(Moducks)
```javascript:modules/todo.js
import { createModule } from 'moducks';
import { select } from 'redux-saga/effects';

const defaultState = {
  list: [],
};

export const {
  todo, sagas,
  addTodo, createTodo,
} = createModule('todo', {
  ADD_TODO: {
    reducer: (state, { payload }) => ({
      ...state,
      list: [...state,list, payload],
    }),
  },
  CREATE_TODO: {
    saga: function* ({ payload }) {
      const { info: userInfo } = yield select(state => state.user);
      return addTodo({ text: payload, userInfo, done: false });
    },
  },
}, defaultState);
```

## selectors(Reselect)
```javascript:selectors/todo.js
import { createSelector } from 'reselect';

export const getTodo = createSelector(
  [
    state => state.todo.list,
    state => state.user.info,
  ], (todoList, userInfo) => {
    // 空チェックとかユーザ情報チェックとか
    const list = todoList.filter(todo => todo.userInfo.id === userInfo.id);
    return {
      list,
      incomplete: list.filter(todo => !todo.done),
      complete: list.filter(todo => todo.done),
    };
  },
);
```

## View/Container
```javascript:containers/TodoContainer.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createTodo } from '~/modules/todo';
import { getTodo } from '~/selectors/todo';

@connect(state => ({
  todo: getTodo(state),
}), {
  createTodo,
})
export default class TodoContainer extends Component {
  // 省略
}
```
Componentは省略。Containerから渡された`todo`と`createTodo`を使ってよしなに。

# テスト
テストにはJestを利用する。
## Moducks
### 構成
基本的には[Moducks公式サンプルのテスト](https://github.com/moducks/moducks/blob/master/examples/randomUser/test/randomUser.js)と同じような構成で。
Moducksモジュールに対して、１モジュール１テストファイル。ActionCreator, Reducer, Sagaのテストを１ファイル内に書く。
構成はこんな感じ。

```javascript:todo.test.js
describe('Todo', () => {
  describe('creators', () => {
    // ActionCreatorのテスト
  });
  describe('reducer', () => {
    // Reducerのテスト
  });
  describe('saga', () => {
    // Sagaのテスト
  });
});
```

### ActionCreator
ActionCreatorのテストは、**ActionCreatorメソッドを呼び出した際に適切なAction、パラメータが発行されているか**のテストを行う。
`addTodo`のテストは以下のような感じ。

```javascript
  describe('creators', () => {
    it('addTodo', () => {
      expect(addTodo({ text: 'hoge' })).toEqual({
        type: 'hoge/ADD_TODO',
        payload: { text: 'hoge' },
      });
    });
  });
```

### Reducer
Reducerのテストは、**ActionCreatorが呼ばれた際にstateの内容が適切に更新されているか**のテストを行う。
`addTodo`のテストは以下のような感じ。

```javascript
  describe('reducer', () => {
    let state;

    /* 初期値のテスト（todo.listは空） */
    it('defaultState', () => {
      state = todo(state, ActionTypes.INIT);
      expect(state).toEqual({
        list: null,
      });
    });

    /* addTodoが呼ばれた場合のテスト */
    it('addTodo', () => {
      state = todo(state, addTodo({ text: 'hoge' }));
      expect(state).toEqual({
        list: [{ text: 'hoge' }],
      });
    });
  });
```

### Sagaのテスト
Sagaのテストは、基本的にはredux-sagaのテストの書き方に習う。
この辺は説明長くなるので[この記事](https://qiita.com/kuy/items/716affc808ebb3e1e8ac)とかで勉強してください（丸投げ

```javascript
  describe('saga', () => {
    it('createTodo -> addTodo', () => {
      const saga = retrieveWorkers(sagas).createTodo(createTodo('hoge'));
      let current = saga.next();

      current = saga.next({ info: { id: 1, name: 'Taro' } }); // yield select(state => state.user)
      expect(current).toEqual({
        done: false,
        value: put(addTodo({ text: 'hoge', userInfo: { id: 1, name: 'Taro' }, done: false })),
      });

      current = saga.next();
      expect(current).toEqual({
        done: true,
        value: undefined,
      });
    });
  });
```

# まとめ
Moducks、Selectorを導入したことでコードが格段に追いやすくなった。Moducksマジ神ですわ。
メンバーにも好評なのでしばらくはこの構成でいこうと思う。