middleware
applyMiddlewareで設定している。
https://github.com/reactjs/redux/blob/master/src/applyMiddleware.js
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がどのようなことを行っているかを見てみる。
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
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を見てみる。
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に設定しなければなりません。
...,
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例
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編に続く・・・(完)