手っ取り早くReduxがどのようなものか理解する~middleware, combineReducers編~

## middleware
applyMiddlewareで設定している。
https://github.com/reactjs/redux/blob/master/src/applyMiddleware.js

```js:applyMiddleware
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, initialState, enhancer) => {
    var store = createStore(reducer, initialState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
```

この通り、createStoreを引数にして、新しいstoreとdispatchを返すfunctionを返しています。(curry化が可能)
middlewareは、middlewareAPIを引数としなければならないことがわかる。

composeがどのようなことを行っているかを見てみる。

```js:compose.js
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  } else {
    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0, -1)
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
  }
}
```

`compose()(hoge)`のように引数がない場合は、hogeだけが返される。
`compose(hoge, foo, bar)(something)`のように引数がある場合は、`last(...args)`を初期値としてreduce(`hoge(foo(bar(something)))`)しています。 
`f = middleware(middlewareAPI)`となり、`composed = f(composed)`となります。
よくある、middlewareでwrapし続ける処理ですね。

## combineReducers
combineReducersで設定している。
https://github.com/reactjs/redux/blob/master/src/combineReducers.js

```js:combineReducers
export default function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  var finalReducerKeys = Object.keys(finalReducers)

  var sanityError
  try {
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }

  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }

    if (process.env.NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    var hasChanged = false
    var nextState = {}
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}
```

複数のreducerを引数にとり、combinationされたreducerを返す。
基本、`state`と`action`を引数に取るfunctionはreducerと考えて良い。
ひとつずつ処理を見ていく。

`var reducerKeys = Object.keys(reducers)`
Object.keysは列挙可能なpropertyしか返さないので、reducersは`{ hoge: (state, action) => ..., foo: (state, action) => ..., ...}`のような形式でなければならない。

`finalReducers[key] = reducers[key]`
functionであるreducerのみがfinalReducersに登録される。

`var finalReducerKeys = Object.keys(finalReducers)`
もう一度keyだけ取り出す。

`assertReducerSanity(finalReducers)`
assertReducerSanityを見てみる。

```js:asserReducerSanity
function assertReducerSanity(reducers) {
  Object.keys(reducers).forEach(key => {
    var reducer = reducers[key]
    var initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
        `If the state passed to the reducer is undefined, you must ` +
        `explicitly return the initial state. The initial state may ` +
        `not be undefined.`
      )
    }

    var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
    if (typeof reducer(undefined, { type }) === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
        `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
        `namespace. They are considered private. Instead, you must return the ` +
        `current state for any unknown actions, unless it is undefined, ` +
        `in which case you must return the initial state, regardless of the ` +
        `action type. The initial state may not be undefined.`
      )
    }
  })
}
```

この通り、reducerはINIT及び定義されていないACTIONに対してinitialStateを返さないとエラーが返ります。
`var reducer = (state = initialState, action) => { ... }`のような形式でinitialStateに設定しなければなりません。

```js
  ...,

  return function combination(state = {}, action) {
    var hasChanged = false
    var nextState = {}
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}
```

`var nextStateForKey = reducer(previousStateForKey, action)`
前のstateから新しいstateを生成します。undefinedの場合はエラーです。

`nextState[key] = nextStateForKey`
reducerの名前に対応した新しいstateが登録されます。

`return hasChanged ? nextState : state`
渡されたreducerdでひとつでもstateが更新されていればnextStateが返され、なにも更新されなければ、渡されたstateが返されます。

#### code例

```js:example
const { combineReducers } = require('redux')

const reducer1 = (state = 0, action) => {
  if (action === 'UPDATE') {
    return state + 1
  } else {
    return state
  }
}

const reducer2 = (state = 'Hello', action) => {
  if (action === 'UPDATE') {
    return state + ', world!'
  } else {
    return state
  }
}

const reducer3 = (state = {}, action) => {
  if (action === 'UPDATE') {
    return {
      a: 'this is a',
      b: 'this is b',
    }
  } else {
    return state
  }
}

const combines = combineReducers({ r1: reducer1, r2: reducer2, r3: reducer3 })

const nextStates = combines({}, 'UPDATE')

console.log(nextStates.r1) // 1
console.log(nextStates.r2) // Hello, world!
console.log(nextStates.r3.a)  // this is a
console.log(nextStates.r3.b)  // this is b
```

react-redux編に続く・・・（完）