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

下面是執行結果

   我們一起分析一下:

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM