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