react+reduxで共通action/reducerを作る

### 1. 概要
react+reduxで大規模SPAを構築する時に、画面ごとに定義されているモデルデータをそれぞれの`Action`で更新しなければならいので、よく似てるモデルデータのやり取りするソースコードをあっちこっちにコピーするのが効率よくない一歩、メンテナンスし難くなるため、成る可くそれを避けたい。

本文はreducerやの共通化のアプローチでそれの解決を目指す。

### 2. なぜ共通action/reducerが必要なのか？

- 共通的なUIコンポーネントが複数画面で使われる、あるいは同じ画面で複数回使われる場合、表示用のデータもそれぞれのモデルに持つ必要がある。
- 該当データの更新処理をそれぞれのaction/reduxを定義するのが、面倒だし、`DRY`に違反する。

##### 例：都道府県・市区町村入力の共通UIコンポーネント`LocationSuggest`を使っている場合、全く一緒なソースコードが生まれる

```
タッチすると都道府県・市区町村のリストが表示され、選択できる。
```

- UIコンポーネント`LocationSuggest.jsx`
- モデルデータ`LocationSuggestRecord.js`

- 画面`PageA`で`LocationSuggest`を使っている

```js
// actionA.js
hoge(value) { disptach({type: 'PAGE_A_CHANGE_WORK_ADDRESS_HOGE', value}) }
fuga(value) { disptach({type: 'PAGE_A_CHANGE_WORK_ADDRESS_FUGA', value}) }
```

```js
// dataA.js
DataA {
  workAddress : new LocationSuggestRecord(),
}
```

```js
// reducerA.js
function(state, action) {
  switch(action.type) {
    case 'PAGE_A_CHANGE_WORK_ADDRESS_HOGE' :
      return state.setIn['workAddress', 'Hoge'], value);
    case 'PAGE_A_CHANGE_WORK_ADDRESS_FUGA' :
      return state.setIn['workAddress', 'Fuga'], value);
  }
}
```

- 画面`PageB`もLocationSuggest`を使っている

```js
// actionB.js
hoge(value) { disptach({type: 'PAGE_B_CHANGE_WORK_ADDRESS_HOGE', value}) }
fuga(value) { disptach({type: 'PAGE_B_CHANGE_WORK_ADDRESS_FUGA', value}) }
```

```js
// dataB.js
DataB {
  workAddress : new LocationSuggestRecord(),
}
```

```js
// reducerB.js
function(state, action) {
  switch(action.type) {
    case 'PAGE_B_CHANGE_WORK_ADDRESS_HOGE' :
      return state.setIn['workAddress', 'Hoge'], value);
    case 'PAGE_B_CHANGE_WORK_ADDRESS_FUGA' :
      return state.setIn['workAddress', 'Fuga'], value);
  }
}
```

### 3. action/reducerを共通化する

共通化するとこうなる。

- アクション`locationSuggestAction.js`

```js
// locationSuggestAction.js
hoge(type, value) { disptach({type, commonType: 'HOGE', value}) }
fuga(type, value) { disptach({type, commonType: 'FUGA', value}) }
```

- Reducer`locationSuggestReducer.js`

```js
// locationSuggestReducer.js
function(state, action) {
  switch(action.commonType) {
    case 'HOGE' :
      return state.setHoge(action.value)
    case 'FUGA' :
      return state.setFuga(action.value)
  }
}
```

- 画面`PageA`で`commonType`指定で共通アクション`locationSuggestAction`を呼び出す

```js
locationSuggestAction.hoge('PAGE_A_CHANGE_WORK_ADDRESS', 'value');
```

- 画面`PageA`の`reducerA`で`commonType`を判断してからで共通recuder`locationSuggestReducer`を呼び出す

```js
// reducerA.js
function(state, action) {
  switch(action.type) {
    case 'PAGE_A_CHANGE_WORK_ADDRESS' :
      // 共通reducerを呼び出す
      return state.set('workAddress', locationSuggestReducer(state.get('workAddress'), action));
    case yy :
      return zz
  }
}
```

- 画面`PageB`も同様

```js
locationSuggestAction.hoge('PAGE_B_CHANGE_WORK_ADDRESS', 'value');
```

- 画面`PageB`の`reducerB`も同様

```js
// reducerB.js
function(state, action) {
  switch(action.type) {
    case 'PAGE_B_CHANGE_WORK_ADDRESS' :
      // 共通reducerを呼び出す
      return state.set('workAddress', locationSuggestReducer(state.get('workAddress'), action));
    case yy :
      return zz
  }
}
```

### 共通action/reducer特徴の纏め
- typeを引数として呼び出す時に渡すだけ、動的にアクション関数を生成はしないので、無駄なレンダリングを防げる
- 今までのアクションとreducer定義のやり方に従っているので、違和感がない