ReduxでReducerとInitialStateを分けるためのbetterCombineReducers

# 要約
[Redux](http://redux.js.org)のReducerは、特に`combineReducers`を使うときには初期状態を含むように書く必要がある。それでは数学的に美しくないし、実際に都合の悪いケースもある。そこで、初期状態を別途与えることができる`betterCombineReducers`を提案したい。それは下記に公開されており、利用することもできる。

GitHub: [shinout/better-combine-reducers](https://github.com/shinout/better-combine-reducers)

# 導入
## ReduxとDFA(決定性有限オートマトン)

Reducerの定義を見て欲しい。

```js
Reducer<S, A> = (state: S, action: A) => S
```
Reducerは、StateとActionから、新しいStateを返す。

このようなシンプルで美しい設計は、離散数学の一分野である[決定性有限オートマトン(DFA)](https://www.jaist.ac.jp/~uehara/course/2011/ti118/07fa.pdf)にそのヒントを得ていると考えられる。その中で出てくる「状態遷移関数」こそがReducerだ。

DFAの考え方に従えば、状態機械というのは

- 状態の集合
- 入力記号の集合
- 状態遷移関数
- 開始状態
- 受理状態の集合

によって定義できる。アプリケーションを状態とその遷移と捉えるReduxは、そのままDFAの概念にマッピングされる。(無限の状態を有限のクラスにまとめたり、受理状態を空集合と捉えたりなどがありそうだがここでは触れない)

DFAとReduxをマッピングすると下記のようになる。

- 状態機械: Store
- 状態: State
- 入力記号: Action
- 状態遷移関数: Reducer
- 開始状態: InitialState
- 受理状態: (受理の定義がないので、なし)

```js
const reducer = (state, action) => state
const store = createStore(reducer, initialState)
store.dispatch(action)
```

コードもそのものを表せている。

## ReduxはInitialStateをReducerの一部とみなしている
このようにDFAとReduxには共通点が多いように思われたが、ひとつ気になる点があった。
それが、Reduxでは**ReducerがInitialStateを知っていることを推奨している**という点だ。
DFAの言葉でいうと**状態遷移関数が開始状態を知っている**という意味になる。
DFAでは、状態機械とは上に挙げた独立5変数によってできていると捉えていたが、ReduxはInitialStateをReducerの一部と捉えているようだ。

```js
const reducer = (state = initialState, action) => {
    switch (action.type) {
    default: break
    }
    return state
}
```

こんな感じで、`state`に`undefined`が入ったときは`initialState`を返すように実装することを推奨しているのだ (See http://redux.js.org/docs/basics/Reducers.html )。


## combineReducersすると、ReducerにInitialStateを書かなくてはならない
Reduxは、複数のReducerを束ねる`combineReducers`という関数を提供しているが、
これを使うと、いよいよReducerがInitialStateを知っていることは、**推奨ではなく必須**となる。

[ReduxのcombineReducersの仕組みについて理解したいマン](https://hogehuga.com/post-1113/)という良記事によれば、`combineReducers`は、その過程でReducerとしての要件を満たしているかを確認する処理`assertReducerSanity`が呼ばれるという。そこでは**stateにundefinedを入れて、非nullな値が返るかどうかを検証している**のだ。その検証が通らなければ、`combineReducers`は失敗する。よって、否が応でも初期状態をReducerに書くことになるのであった。

### なぜReducerにInitialStateを書きたくないのか
これは、DFAとの対応が崩れてしまうという、理論上の話にとどまらない。
ここでは、次のよくありそうな要件のアプリを考える。

- アプリ終了時に、Reduxの最後のStateをJSONとして保存
- 再開時にそれをInitialStateとする

このように**InitialStateが状況に応じて変わることがあるアプリ**だと、ReducerにInitialStateを書くことで不都合が生じる。
初期状態は毎回変わるので、初期状態がReducerに含まれていたらいけない。

初期状態を束縛するためにcreateReducerすることで対応可能だが、そのままやると見た目もひどい。

```js
const createReducer = initialState => (state, action) => {
    if (state == null) return initialState
}
```
実際にはcombineReducersの事も考えると、各キーごとにこの作業をするので、より汚くなる。


# `betterCombineReducers`の提案

そこで提案するのが、下記のようにつかえる関数だ。

```js
const reducer = betterCombineReducers(original)(reducers, initialState)
```

複数のReducerと、それと同じkeyをもったInitialStateを与えると、combineしてくれる。
`original`は、Reduxの提供する、元の`combineReducers`である。


## コード

```js
const betterCombineReducers = (original) => (reducers, initialState) => {
    const newReducers = {}

    Object.keys(reducers).forEach(key => {
        const fn = reducers[key]
        const defaultState = initialState[key]
        newReducers[key] = (state, action) => {
            if (state == null) return defaultState
            return fn(state, action)
        }
    })
    return original(newReducers)
}
```
各キーごとに初期状態を取り出して、デフォルト値としている。
なお[実際のコード](https://github.com/shinout/better-combine-reducers/blob/master/index.js)はエラーハンドリングやIE8のことも考えているので多少冗長である。

# まとめ
Reduxの初期状態と遷移関数は極力分けて管理するほうが美しさも実益もあると考えられる。
それを助けるための[betterCombineReducer](https://github.com/shinout/better-combine-reducers)、お試しを。