combineReducers:把recuder函數們,合并成一個新的reducer函數,dispatch的時候,挨個執行每個reducer
我們依舊先看一下combineReduers的使用效果
let { createStore, bindActionCreators, combineReducers } = self.Redux
//預設state
let todoList = [], couter = 0
// reducer
let todoReducer = function (state = todoList, action) {
switch (action.type) {
case 'add':
return [...state, action.todo]
case 'delete':
return state.filter(todo => todo.id !== action.id)
default:
return state
}
},
couterReducer = function (state = couter, action) {
switch (action.type) {
case 'add':
return ++state
case 'decrease':
return --state
default:
return state
}
}
var reducer = combineReducers({ todoReducer, couterReducer })
//建立store
let store = createStore(reducer)
//訂閱
function subscribe1Fn() {
// 輸出state
console.log(store.getState())
}
store.subscribe(subscribe1Fn)
// action creater
let actionCreaters = {
add: function (todo) { //添加
return {
type: 'add',
todo
}
}, delete: function (id) {
return {
type: 'delete',
id
}
}
}
let boundActions = bindActionCreators(actionCreaters, store.dispatch)
console.log('todo add')
boundActions.add({
id: 12,
content: '睡覺覺'
})
let boundAdd = bindActionCreators(actionCreaters.add, store.dispatch)
console.log('todo add')
boundAdd({
id: 13,
content: '陪媳婦'
})
let counterActionCreater = {
add: function () {
return {
type: 'add'
}
},
decrease: function () {
return {
type: 'decrease'
}
}
}
let boundCouterActions = bindActionCreators(counterActionCreater, store.dispatch)
console.log('counter add:')
boundCouterActions.add()
console.log('counter decrease:')
boundCouterActions.decrease()
下面是執行結果

我們一起分析一下:
- 執行todo add的時候,看到counterReducer和 todoReducer的資料都有更新,說明兩個reducer都執行了。
- 執行counter add的時候,同樣兩個recuder都執行,但是因為沒有參數,加入的是無效資料,這裡就提示我們,是不是該進行一些必要的參數判斷呢
- 執行counter decrease的時候,同樣兩個reducer都執行,但是 todoReducer沒有tyepe為decrease的action處理函數,當然沒有任何産出
我們再回歸源碼,删除一些判斷的代碼邏輯,簡化後如下:
- 過濾一下reducer,把reducer和key都存起來
- 傳回一個新的reducer函數,新的reducer函數執行的時候,便利存起來的reducer,挨個執行
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
這裡額外的分析一下,當store的recuder是複合型的時候,怎麼初始化state的
createStore傳入的第一個參數recuder,是調用 combineReducers 新生成的reducer(依舊是一個函數)
createStore方法傳回之前,會這樣一下dispatch({ type: ActionTypes.INIT }),disptach的裡面我們就關心下面幾句
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
也就是執行一下新的reducer,我們繼續切換到新的reducer代碼裡面,同樣隻關心下面的代碼
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
我們就看我們這個例子 combineReducers({ todoReducer, couterReducer }), 那麼上面的key就會是 todoReducer, couterReducer, 那麼初始化完畢的state得資料結構就是這樣的
{todoReducer:....,couterReducer:......},
有人會想,初始化state,你上次不是用了兩種方式,我這裡隻能說對不起,當你用的是複合型的reducer初始化state的時候,你用第二個參數來初始化state行不通的,
因為為了友善解析代碼,上面我是省略了一部分的 ,下面再看看更完整一點的代碼(我還是省略了一下)
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
.......
}
這裡如果你沒通過 aessertRecucerShape檢查,是沒法進行下去的,我們那看看aessertRecucerShape是個啥玩意,看備注。
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT }) // 傳入 undefined,讓recuder預設值生效,
if (typeof initialState === 'undefined') { // 如果沒有預設值,傳回的state就是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. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
)
}
const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
if (typeof reducer(undefined, { type }) === 'undefined') { // 這個主要是防止在recuder你真的自己定義了對type為ActionTypes.INIT處理,建立一個随機的type,測試一下,你應該傳回的是有效的state
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, but can be null.` ) } }) }
這就說明了上述的問題,(-_-)
回顧
1. combineReducers 的參數是一個對象
2. 執行結果傳回的依舊是一個reducer
3. 通過combineReducers 傳回的reducer建立的store, 再派發某個action的時候,實際上每個内在的reducer都會執行
4. createStrore使用合成的reducer建立的store, 他再派發action傳回的是總的大的state