Redux源码分析(4) - combineReducers和 bindActionCreators

1、前言

   combineReducers 和 bindActionCreators 是 Redux 提供的两个辅助函数,即使不使用他们,也能够正常的使用 Redux。 但是如果利用好这两个辅助函数,将会使得你的代码更加易于维护,特别是 combineReducers 。

2、combineReducers

  在之前的章节中多次提到combineReducers,实际开发中所有 state 和 改变 state 的逻辑放在一个 reducer 中显然是不合适,combineReducers 的意义就在对各个业务模块的 reducer 进行组合,最后传递给 redux 的 createStore 方法,继而生成一个的状态树。

   首先来看下官方文档中关于 reducer 的介绍:

combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。

合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。

   以当前实例说明, combineReducers 组合的 key 分别为 “todos” 、“visibilityFilter” 。 则返回的 state 也是 key 为 “todos” 、“visibilityFilter” 的对象。

export default combineReducers({
  todos,
  visibilityFilter
})

// 返回的state 

state = {
    todos:[],
    visibilityFilter:'SHOW_ALL'
}
2.1 combineReducers 的源码结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6099rOfk-1587870811950)(https://note.youdao.com/yws/api/personal/file/375F69FF905540E7B60AF01958B862DB?method=download&shareKey=6ee0f0dce3fe8daa3a8e475644f8b0c0)]

2.2 combineReducers 的源码
export default function combineReducers(reducers) {
    // reducers中key的数组
    const reducerKeys = Object.keys(reducers);
    // 最终的reducer
    const finalReducers = {};
    for (let i = 0; i < reducerKeys.length; i++) {
        // 接受当前的key
        const key = reducerKeys[i];
        
        //边界判断。。。
        
        // reducer必须是一个function ,过滤出不符合要求的reducer
        if (typeof reducers[key] === 'function') {
            finalReducers[key] = reducers[key];
        }
    }
    // 符合规范的reducer的key数组
    const finalReducerKeys = Object.keys(finalReducers);

    //边界判断。。。

    // 返回function, 对应为createStore 中的  currentReducer。因为 reducer的形式是:(state,action) => { return state}
    // 为了保持数据的一致性,combination 也是形如: state,action) => { return state}
    return function combination(state = {}, action) {
        // reducer不规范报错
        if (shapeAssertionError) {
            throw shapeAssertionError;
        }

        // 边界检查
        if (process.env.NODE_ENV !== 'production') {
            //……
        }

        let hasChanged = false;
        const nextState = {};
        for (let i = 0; i < finalReducerKeys.length; i++) {
            // 获取finalReducerKeys的key和value(reducer)
            const key = finalReducerKeys[i];
            const reducer = finalReducers[key];
            // 当前key的对应的state
            const previousStateForKey = state[key];
            // 执行reducer, 返回当前state
            // 如果 action 在当前 reducer中,则改变当前 reducer的 state 的值
            // 如果 action 不在当前 reducer中,则执行当前 reducer的 default 分支,也即当前reducer的 state 不会改变。
            const nextStateForKey = reducer(previousStateForKey, action);
            // 不存在返回值报错
            if (typeof nextStateForKey === 'undefined') {
                const errorMessage = getUndefinedStateErrorMessage(key, action);
                throw new Error(errorMessage);
            }
            // 新的state放在nextState对应的key里
            nextState[key] = nextStateForKey;
            // 判断新的state是不是同一引用, 以检验reducer是不是纯函数
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
        }
        // 改变了返回nextState
        return hasChanged ? nextState : state;
    };

}

   combineReducers 本身是一个高阶函数,返回的是 combination 的函数。不管是单个 reducer ,还是通过 combineReducers 组合生成的结果,最终在 createStore 方法执行是都会被作为 reducer 参数(此处的 reducer 指的是形参)。因为为了保持数据结构的一致性 , combination 方法也具有与 reducer 相同的形式, 接受 state 和 action 参数,并返回新的 state。

 (state , action) => { ....  return state}

   其中 combineReducers 在执行时,会过滤不符合规范的 reducer,通过闭包的形式,保留两个变量 finalReducers 、 finalReducerKeys。

   由上边分析可知,combination 将作为最终的 reducer 参数传递给 createStore 方法, 并返回一个 store ,当 store.dispathch(action) 发生时,实际执行下边语句。

   currentState = currentReducer(currentState, action);

  其中 currentReducer 就是 combination 方法, combination 接受当前的 state : currentState 和 action 作为参数,主要功能如下:

  • 1、循环遍历 finalReducerKeys (闭包变量),根据当前的 key 找到 finalReducers 中对于的 reducer , 根据当前 key 找到当前 state (currentState)中对应的 state (previousStateForKey);

  • 2、执行 reducer 方法 ,并传入与 reducer 的 key 值对应的 state 的值 (previousStateForKey),以及 action,得到当前 reducer 的最新值 nextStateForKey ,同时将 nextStateForKey 存储在 nextState 的 key值属性上(这就是最终的state 为什么 会根据 combineReducers 组合时的 key 值进行分组的原因

    (1)、如果 action 在当前的 reducer 中,则执行对应的 action.type 分支的代码, 改变 state 的值,并且返回;

    (2)、如果 action 不在当前的 reducer 中,则执行对应的 default 分支的代码, 则会返回原来的 state (同一引用地址);

  • 3、如果执行 reducer 后返回的 state (nextStateForKey) 与 之前的 state (previousStateForKey) 一致的话,就认为state没有发生改变,如果不一致的话 hasChanged 则为 true。

  • 4、根据 hasChanged 是否为 true 来确定返回值是 nextState 还是 state。

2.3 小结

   combineReducers 的意义在于解决 reducer 的分模块处理,在复杂业务开发中,能够解耦各个 reducer 的关系,更利于代码维护。

  • 1、combineReducers 通过各个 reducer 组合时的 key 区分管理,形成一个状态树。
  • 2、获取 state 中的值是,也需更根据对应的 key 值去获取需要的 state 值;
  • 3、dispatch操作时,执行执行 reducer 后,也会根据当前 reducer 对应的 key 值,去改变 state 中当前 key 值对应的那部分数据。

3、bindActionCreators

   能够用到 bindActionCreators 的地方就是 react-redux 中的 mapDispatchToProps 方法。先来看下官方是怎么解释这个 api 的,如下所示:

把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。

一般情况下你可以直接在 Store 实例上调用 dispatch。如果你在 React 中使用 Redux,react-redux 会提供 dispatch 函数让你直接调用它 。

   为了理解这段话,这里有必要解释一下 react-redux 的 connect 方法。其中此处涉及到 connect 的第二个参数 mapDispatchToProps ,这个方法的作用可以简单解释为:View 层 (UI Component)如何去改变 store ,通过将 store 的 dispatch 方法注入到 UI Component 的 props 中。

   mapDispatchToProps 可以三种类型(具体参考react-redux)

  • 不传参数,则直接将 dispatch 作为参数注入到组件的 props 属性中;
  • 传递函数,则函数的入参为 dispatch,返回的对象注入到组件的 props 中;
  • 传递对象(actionCreator),则 react-redux 会在内部调用 bindActionCreators,将返回结果注入到组件的 props 属性中。
3.1 bindActionCreators的使用

  看下具体的实例,为了解释 bindActionCreators ,先从 mapDispatchToProps 为函数时开始分析。


// 定义三个 actionCreator
const increment = () => ({ type: "INCREMENT" });
const decrement = () => ({ type: "DECREMENT" });
const reset = () => ({ type: "RESET" });


//mapDispatchToProps 为函数
const mapDispatchToProps = dispatch => {
  return {
    // 分发由actionCreator创建的actions
    increment: () => dispatch(increment()),
    decrement: () => dispatch(decrement()),
    reset: () => dispatch(reset())
  };
};


connect(null,  mapDispatchToProps)(Component);

   上边的代码能够正常的运行,但是 mapDispatchToProps 的返回对象中,每次都要调用 dispatch 方法。能不能简化了,通过 bindActionCreators 是可以做到的,如下:

//mapDispatchToProps 为函数
function mapDispatchToProps = dispatch => {
  return bindActionCreators({ increment, decrement, reset }, dispatch);   //语句(1)
}

connect(null,  mapDispatchToProps)(Component);

   通过 bindActionCreators 处理之后,二者效果相相同,组件都能接收到 props.increment 、 props.decrement
、props.reset 三个方法。上边为了解释 bindActionCreators 的作用,设置 mapDispatchToProps 为函数,并在 mapDispatchToProps 内部调用 bindActionCreators 方法,实际完全可以不用这样做,还有更简单的的方法,即直接将 actionCreator 合并为一个对象,直接作为 mapDispatchToProps 使用。

//mapDispatchToProps 为函数
function mapDispatchToProps = {
  increment,
  decrement,
  reset
};

connect(null,  mapDispatchToProps)(Component);

   为什么 mapDispatchToProps 能够直接使用 actionCreator 的对象 ,这得益于 react-redux 的处理,这里截取 react-redux 的相关源码:

export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
  return mapDispatchToProps && typeof mapDispatchToProps === 'object'
    ? wrapMapToPropsConstant(dispatch =>
        bindActionCreators(mapDispatchToProps, dispatch)
      )
    : undefined
}

   这段源码的大致意思是: 当 mapDispatchToProps 是一个对象时,就会执行 wrapMapToPropsConstant 方法,该方法的入参正是:

dispatch =>  bindActionCreators(mapDispatchToProps, dispatch)
      

   这段代码的的作用,跟我们上边的实例中语句 (1) 是完全一致的,都是执行 bindActionCreators 方法,其中入参是 actionCreator 和 dispatch (store.dispatch)方法。

3.2 bindActionCreators的源码

   bindActionCreators的源码比较简单,核心逻辑如下:

function bindActionCreator(actionCreator, dispatch) {
  // 返回函数,具体执行在 this.props.increment(...args)
  return function() {
    // 执行后返回结果为传入的actionCreator直接调用arguments
    return dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {
  //……

  // 获取actionCreators的key
  const keys = Object.keys(actionCreators)
  // 定义return 的props
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    // actionCreators的key, es6 对象的简写方式方式下:即对应函数名
    const key = keys[i]
    // 获取对象的 actionCreator
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      // 执行 bindActionCreator (高阶函数),返回一个函数
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  // return 的props
  return boundActionCreators
}

   bindActionCreators 方法中,首先遍历 actionCreators 对象 (通过 key 值),并通过 bindActionCreator 方法对每个对象元素 actionCreator 处理,处理后按照对象的 key 重新保存在 boundActionCreators 对象上,这个对象也是注入到 UI Component 的 props ,以上边的实例来说明:


// actionCreator

const increment = (...args) => ({ type: "INCREMENT" , //...... args的处理 });

 // boundActionCreators 的形式
 
{
    increment : function() {
        return dispatch(actionCreator.apply(this, arguments))
  }
}

// UI Component 组件调用该方法时:

this.props.increment();

//实际执行,其中 increment 如果含有入参,则可以通过 arguments 参数传到  actionCreator 中。

dispatch({ type: "INCREMENT" })

3.3 小结

   bindActionCreators 方法本质: 统一处理 actionCreator 的 dispatch 。使用场景在 mapDispatchToProps 中,当 mapDispatchToProps 是对象时 (必须符合actionCreator),react-redux 会自己调用 bindActionCreators 处理;


Redux源码分析(1) - Redux介绍及使用
redux源码分析(2) - createStore
Redux源码分析(3) - applyMiddleware
Redux源码分析(4) - combineReducers和 bindActionCreators

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值