微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

Connect工具类篇

这里讲的其实是connect方法的基础,本来是准备先把connectAdvanced和Provider写完,再专门写connect和相关方法的,但是发现connectAdvanced也用到了很多这些基础方法,所以就停下来,写下面的这些东西。

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

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

dependsONownProps

单独提出来这个属性,做一个简单的说明。dependsONownProps属性并不是对外的属性,而是代码内部逻辑使用的,会在多个方法中用到,这个属性主要是针对connect的两个参数mapStatetoProps,mapdispatchToProps。

根据文档,mapStatetoProps可以是function或者null,mapdispatchToProps可以是function,object或者null。如果是function,当定义的时候,可以选择是否传入ownProps对象,比如function mapStatetoProps(state,ownProps) {},这就说明这个function的返回结果可能是基于ownProps的,所以每次ownProps发生改变的时候,都需要调用这个方法进行更新。

所以dependsONownProps就是当ownProps更新的时候,用来判断是否需要重新调用对应方法获取新的结果。

wrapMapToProps.js

getDependsONownProps

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

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

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

这里有几种情况:

  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都会被认为是一个Reduxaction 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,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,ownProps) {
      const nextMergedProps = mergeProps(stateProps,ownProps)

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

      } else {
        hasRunOnce = true
        mergedProps = nextMergedProps

        if (process.env.NODE_ENV !== 'production')
          verifyPlainObject(mergedProps,'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)》

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...