天天看點

Redux源碼分析之combineReducers

Redux源碼分析之基本概念 Redux源碼分析之createStore Redux源碼分析之bindActionCreators Redux源碼分析之combineReducers Redux源碼分析之compose Redux源碼分析之applyMiddleware 

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()      

下面是執行結果

Redux源碼分析之combineReducers

   我們一起分析一下:

  • 執行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

繼續閱讀