zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    二叉树遍历问题、时间空间复杂度、淘汰策略算法、lru数据结构、动态规划贪心算法
    Django--csrf跨站请求伪造、Auth认证模块
    Django--中间件
    Django--Cookie和Session组件
    Django--form表单组件
    安装配置flask环境
    Django--模型层
    Django--路由层、视图层、模版层
    Eclipse SVN文件冲突及不能直接提交情况
    Eclipse开发Web常见异常
  • 原文地址:https://www.cnblogs.com/cloud-/p/7277769.html
Copyright © 2011-2022 走看看