React Redux: 从文档看源码 - Connect工具类篇(1)

注:这篇文章只是讲解React Redux这一层,并不包含Redux部分。Redux有计划去学习,等之后学习了Redux源码之后再作分析;
注:代码基于如今 (2016.12.29) React Redux 的最新版本 (5.0.1)。javascript

Connect工具类篇

这里讲的实际上是connect方法的基础,原本是准备先把connectAdvanced和Provider写完,再专门写connect和相关方法的,可是发现connectAdvanced也用到了不少这些基础方法,因此就停下来,写下面的这些东西。html

以前没有上过代码结构图,这里贴张图上来(5.0.1版本)
clipboard.pngjava

React Redux在最近的几个版本中对代码作了拆分和优化,以前看4.x的代码的时候,connect下面全部的方法都在一个文件里面,并且逻辑也不够清晰。当时原本想吐槽他们的源代码的,可是升级到5.x之后,就发现代码清晰不少,只不过代码逻辑复杂度更上一层楼。。。react

dependsOnOwnProps

单独提出来这个属性,作一个简单的说明。dependsOnOwnProps属性并非对外的属性,而是代码内部逻辑使用的,会在多个方法中用到,这个属性主要是针对connect的两个参数mapStateToProps, mapDispatchToProps。git

根据文档,mapStateToProps能够是function或者null, mapDispatchToProps能够是function, object或者null。若是是function,当定义的时候,能够选择是否传入ownProps对象,好比function mapStateToProps(state, ownProps) {},这就说明这个function的返回结果多是基于ownProps的,因此每次ownProps发生改变的时候,都须要调用这个方法进行更新。github

因此dependsOnOwnProps就是当ownProps更新的时候,用来判断是否须要从新调用对应方法获取新的结果。redux

wrapMapToProps.js

getDependsOnownProps

这段代码主要的做用就是判断map(State/Dispptch)ToProps方法是否须要ownProps。
返回值决定了在props更新的时候,是否要调用map(State/Dispptch)ToProps方法进行更新segmentfault

export function getDependsOnOwnProps(mapToProps) {
  return (mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined)
    ? Boolean(mapToProps.dependsOnOwnProps)
    : mapToProps.length !== 1
}

这里经过判断方法的length来进行判断是否须要ownProps,当mapToProps.length !== 1的时候,就是须要。反之,就是不须要。 数组

这里有几种状况:app

  1. function mapStateToProps(state){},那么mapStateToProps.length === 1,不须要ownProps

  2. function mapStateToProps(state, ownProps){}, 那么mapStateToProps.length === 2, 须要ownProps

  3. function mapStateToProps(){ arguments[0]; } 或者 function mapStateToProps(...args){}, 那么mapStateToProps.length === 0, 因为没法经过定义判断是否须要ownProps,因此默认是须要

  4. 若是以前已经设置过了,那么就不须要设置了,重用Boolean(mapToProps.dependsOnOwnProps)

wrapMapToPropsConstant

首先,根据文档mapDispatchToProps是optional的,并且能够是function或者object。
这块代码主要针对mapDispatchToProps是object或者null的状况,把这个对象进行包装,生成(dispatch, options)=>()=>dispatchedActions的方法,并添加dependsOnOwnProps属性。

export function wrapMapToPropsConstant(getConstant) {
  return function initConstantSelector(dispatch, options) {
    const constant = getConstant(dispatch, options)

    function constantSelector() { return constant }
    constantSelector.dependsOnOwnProps = false 
    return constantSelector
  }
}

首先查看一下这个方法的调用,在mapDispatchToProps.js里面有两处调用:

export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {...}

export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
  return (!mapDispatchToProps)
    ? wrapMapToPropsConstant(dispatch => ({ dispatch })) // 会生成(dispatch, options)=>()=>({dispatch})
    : undefined
}

export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
  return (mapDispatchToProps && typeof mapDispatchToProps === 'object')
    ? wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch)) // 会生成(dispatch, options)=>()=>bindActionCreators(mapDispatchToProps, dispatch),也就是绑定过dispatch的actions
    : undefined
}

在这里,若是mapDispatchToProps为null或者是object的时候,调用wrapMapToPropsConstant方法。
而在Object里面,因为调用了bindActionCreators方法,因此最后生成的都是绑定过dispatch的actions,这也就是为何在connect的组件中,直接调用this.props.action()就能够通知redux,而不是dispatch(this.props.action()).

文档中也提到:

若是传入的时object,那么里面全部的function都会被认为是一个Redux的action creator。同时,一个影对象会被造出并合并到组件的props中。这个影对象会包含mapDispatchToProps中一样的function名,可是每个action creator都被dispatch包裹,因此就能够直接调用。

注:whenMapDispatchToPropsIsObject只是返回(dispatch, options)=>()=>disptachedActions,而后会在selectorFactory里面传入dispatch和options,并根据dependsOnOwnProps调用,最后得到里面的constant。最后会在mergeProps方法里面,把state, constant, props进行合并

wrapMapToPropsFunc

这个方法主要是针对mapStateToProps和mapDispatchToProps是function的状况。

mapStateToProps

若是传入了这个function,那么组件就会监听Redux store的更新。一旦store发生更新,那么mapStateToProps就会被调用。返回的结果必须是一个plain object,结果会被合并到组件的props中。若是没有传入这个对象,那么组件就不会监听Redux Store。若是ownProps在定义中做为一个参数,那么ownProps的值就是传入组件的props,同时每次props发生改变,mapStateToProps就会被从新调用。(若是组件接受的props发生了浅层改变(shallowly changed),同时ownProps也做为一个参数被传入,那么mapStateToProps就会被从新调用)

mapDispatchToProps

若是传入的是function,那么dispatch会被传入到这个function中。你能够按照本身的意愿返回被dispatch绑定过的action creators.

两个都有

Note: 在一切特殊状况下,你可能须要对渲染性能有更多的掌控,mapDispatchToProps()和mapStateToProps()也能够返回一个function。在这种状况下,返回的function会被做为真正的mapDispatchToProps(mapStateToProps)。这么作,容许你能够作一些记忆类的操做(应该是说,能够记录上一次的state和ownProps,在function里面能够作对比,减小没必要要的改变)

代码是这样子的:

export function wrapMapToPropsFunc(mapToProps, methodName) {
  return function initProxySelector(dispatch, { displayName }) {
    const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      return proxy.dependsOnOwnProps
        ? proxy.mapToProps(stateOrDispatch, ownProps)
        : proxy.mapToProps(stateOrDispatch)
    }

    proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps) // 初始化时根据mapToProps来判断是否须要ownProps

    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
      // 第一次调用的时候,会进到这里
      proxy.mapToProps = mapToProps
      let props = proxy(stateOrDispatch, ownProps) // 先获取mapToProps的返回值

      if (typeof props === 'function') { // 若是返回值是function,那么符合文档中说的特殊状况
        proxy.mapToProps = props // 把这个props看成真正的mapToProps
        proxy.dependsOnOwnProps = getDependsOnOwnProps(props) // 根据新的props方法来更新是否须要ownProps
        props = proxy(stateOrDispatch, ownProps) // 获取最终结果
      }

      if (process.env.NODE_ENV !== 'production') 
        verifyPlainObject(props, displayName, methodName) // 若是是非production环境下,判断结果的类型

      return props
    }

    return proxy
  }
}

这里的mapToProps就是map(State/Dispatch)ToProps的意思,这个代码主要作了几个工做:

  1. 判断这个方法是否基于ownProps

  2. 第一次调用的时候(或者说connect方法初始化的时候),若是mapToProps返回的时一个function,那么就把这个function看成真正的mapToProps

  3. 第一次调用的时候,检查返回的对象是否是一个plain object, 若是不是,那么就在非production环境下抛出一个异常,提示开发者

注: 从代码里面看出一点,就是当function返回function的状况,其实dependsOnOwnProps并非根据外层的function来定的,而是根据返回的function而定的。并且,像文档中所说,他的主要做用是作对比(和上一个state, ownProps),因此我猜代码应该像这个样子

function mapStateToProps(state, ownProps){ // ownProps可选
    let oldState = state, oldProps = ownProps, lastState;
    return function(state, ownProps){ // ownProps可选
      // 在这里对比当前state, ownProps和以前的oldState, oldProps,来生成新的state,或者直接用以前的state
      let ret = {};
      if(!lastState) {
        lastState = state; // do some computation here.
      } else if(!shallowEqual(state, oldState) || !shallowEqual(oldProps, ownProps)) {
        lastState = state; // do some computation here
      }
      
      oldState = state;
      oldProps = ownProps;

      return lastState;
    }
  }

同时,真正是否渲染根据ownProps改变,是基于内层的function来定的。因此说:

dependsOnOwnProps为false

function mapStateToProps(state, ownProps){
    return function(state){ }
  }

dependsOnOwnProps为true

function mapStateToProps(state){
    return function(state, ownProps){ }
  }

mapDispatchToProps.js

这个JS中提供了三个方法,分别应对mapDispatchToProps是function,object和null的状况。

whenMapDispatchToPropsIsFunction

export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
  return (typeof mapDispatchToProps === 'function')
    ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
    : undefined
}

当mapDispatchToProps是function的时候,用wrapMapToPropsFunc来进行调用。最后返回的应该是(dispatch, {displayName})=>(dispatch, ownProps)=>binded Action Creators

whenMapDispatchToPropsIsMissing

export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
    return (!mapDispatchToProps)
      ? wrapMapToPropsConstant(dispatch => ({ dispatch }))
      : undefined
  }

当mapDispatchToProps是null的时候,调用wrapMapToPropsConstant(dispatch => ({ dispatch })),这里这么作的目的是,只把dispatch绑定到props上面。这里返回的是(dispatch, options)=>()=>{ dispatch }

whenMapDispatchToPropsIsObject

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

当mapDispatchToProps是object的时候,调用wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch))进行包装,根据以前对wrapMapToPropsConstant的介绍,这里返回的是(dispatch, options)=>()=>binded Action Creators

最后的export default

export default [
  whenMapDispatchToPropsIsFunction,
  whenMapDispatchToPropsIsMissing,
  whenMapDispatchToPropsIsObject
]

这里写的颇有趣,也很值得借鉴。先看一下调用的地方:

const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
function match(arg, factories, name) {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg)
    if (result) return result
  }

  return (dispatch, options) => {
    throw new Error(`Invalid value of type ${typeof arg} for ${name} argument when connecting component ${options.wrappedComponentName}.`)
  }
}

这里mapDispatchToPropsFactories是以前的数组。因为这三个方法若是类型判断不正确,就会返回undefined,因此这里的match经过循环的方法来进行判断和解析。若是result存在,那么就说明判断经过,就返回。若是result不存在,那么就检查下一个。

若是按照平时的写法,我确定会用不少的if来进行判断,这样子反而增长了代码量,并且不美观。好比这样:

export default function(mapDispatchToProps){
  let result = undefined;
  
  result = whenMapDispatchToPropsIsFunction(mapDispatchToProps);
  if(!result) result = whenMapDispatchToPropsIsMissing(mapDispatchToProps);
  if(!result) result = whenMapDispatchToPropsIsObject(mapDispatchToProps);

  return result;
}

和源码里的比较,感受仍是源码里写的好看,有趣。

mapStateToProps.js

这个JS中提供了两个方法,分别应对mapDispatchToProps是function和null的状况。

whenMapStateToPropsIsFunction

export function whenMapStateToPropsIsFunction(mapStateToProps) {
  return (typeof mapStateToProps === 'function')
    ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
    : undefined
}

当mapStateToProps是function的时候,进行封装。这里返回的结果是(dispatch, {displayName})=>(state, ownProps)=>state result

whenMapStateToPropsIsMissing

export function whenMapStateToPropsIsMissing(mapStateToProps) {
  return (!mapStateToProps)
    ? wrapMapToPropsConstant(() => ({}))
    : undefined
}

这里,当mapStateToProps是null的时候,返回结果是(dispatch, {displayName})=>(state, ownProps)=>{}。之因此这里返回的是{},而不是undefined后者null,是由于便于之后在mergeProps的时候不须要再去检查undefined和null的状况。

export default

export default [
  whenMapStateToPropsIsFunction,
  whenMapStateToPropsIsMissing
]

mergeProps.js

这个JS中提供了两个方法,分别应对mergeProps是function和null的状况。

插一段mergeProps的文档:

若是这个值不是undefined,那么它会接受mapStateToProps(), mapDispatchToProps()和父组件传入的props。这个方法返回的plain object会被看成组件的props传给组件。你能够在这个方法里面只选择部分props返回,或者根据传入的props给action creators绑定一些参数。若是没有传这个值,默认值是Object.assign({}, ownProps, stateProps, dispatchProps)

defaultMergeProps

export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
  return { ...ownProps, ...stateProps, ...dispatchProps }
}

默认的mergeProps方法。和文档中说的同样,不用解释。

wrapMergePropsFunc

export function wrapMergePropsFunc(mergeProps) {
  return function initMergePropsProxy(
    dispatch, { displayName, pure, areMergedPropsEqual } // 后面的其实都是option里面东西
  ) {
    let hasRunOnce = false // 第一次不用检查,直接赋值,因此有一个flag
    let mergedProps // 记录props,用于赋值和对比

    return function mergePropsProxy(stateProps, dispatchProps, ownProps) {
      const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps)

      if (hasRunOnce) {
        if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))
          mergedProps = nextMergedProps

      } else {
        hasRunOnce = true
        mergedProps = nextMergedProps

        if (process.env.NODE_ENV !== 'production')
          verifyPlainObject(mergedProps, displayName, 'mergeProps')
      }

      return mergedProps
    }
  }
}

在这里先说一下pure这个option

pure是connect的第四个参数option中的一项,默认值是true。在这里的定义是,一个组件是pure的话,就说明这个组件并不依赖于除了props和Redux store的state意外的任何输入和状态。若是pure是true,当相关的props和state通过对比,并无发生改变的话,那么就不去调用mapStateToProps,mapDispatchToProps和mergeProps。反之,不管是否改变,都会调用这些方法更新props。(注: 这里的通过对比,在这里方法里是areMergedPropsEqual这个对比的方法。另外还有areStatesEqual, areOwnPropsEqual, areStatePropsEqual都是在option中定义)

这里作了几件事情:

  1. 第一次调用的时候,不进行是否改变的判断,同时检查返回值的格式

  2. 第二次之后的调用,都会进行pure和改变的判断,若是改变了,才修改mergedProps值,减小了没必要要的渲染

export default

export function whenMergePropsIsFunction(mergeProps) {
  return (typeof mergeProps === 'function')
    ? wrapMergePropsFunc(mergeProps)
    : undefined
}

export function whenMergePropsIsOmitted(mergeProps) {
  return (!mergeProps)
    ? () => defaultMergeProps
    : undefined
}

export default [
  whenMergePropsIsFunction,
  whenMergePropsIsOmitted
]

和以前的几个js同样,包含isFunction和isOmitted两个方法。

一点总结

  1. 能够利用function.length来判断这个function在定义的时候有几个参数。能够用来判断某些参数是否须要传入,或许能够减小没必要要的计算。可是,须要注意的是,当function中使用arguments,或者function(...args){}的时候,虽然内部可能会使用多个参数,可是length返回0,没法经过length属性进行判断。

  2. 因为stateProps和dispatchProps是否根据ownProps来进行更新是根据function.length来定的,因此若是不须要,就不要在定义的时候加上这个参数。

  3. 须要处理一个对象,这个对象可能有多种类型的时候,咱们能够不选择在方法写if..else来判断类型,能够像mapDispatchToProps, mapStateToProps同样,返回一个function的数组,每一个function里面判断是不是符合的类型,是的话,按这个类型处理。不是的话,返回undefined。简单,好用。

  4. 当一个系统接受的同一个参数多是多种不一样的类型,该怎么办?咱们并不指望在里面使用过多的typeof, if...else来进行判断,代码会显得比较啰嗦臃肿。咱们能够考虑把全部类型都转化成为统一的一个类型。就像这里的wrapMapToProps同样,把Object,function, null都转成function,之后就不须要多余处理类型了

下篇:《Connect工具类篇(2)》

相关文章
相关标签/搜索