庖丁解牛React-Redux(二): connect

前端之家收集整理的这篇文章主要介绍了庖丁解牛React-Redux(二): connect前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

connect API

  上篇文章庖丁解牛React-Redux(一): connectAdvanced介绍了react-redux的ProviderconnectAdvanced几个重要API的原理,其中connectAdvancedconnect函数的基础,这篇文章将主要介绍connect函数的原理。之前没有阅读过connectAdvanced最好提前阅读一下这篇文章。之前的文章有读者反映看起来比较晦涩,所以我准备随后会出一篇关于类似图解connectAdvanced文章,不讲代码,主要从原理的方面诠释connectAdvanced。再次做个广告,欢迎大家关注我的掘金账号和我的博客

  最开始我们还是来介绍一下connect函数:
  

connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])

  将React组件连接到Redux store,connect函数connectAdvanced的正面,为大多数常见场景提供了易于使用的API。connect函数并不会修改传递的组件,相反,它会返回一个新的,连接到store的组件类。

参数:

  • [mapStateToProps(state,[ownProps]): stateProps](Function):
    如果这个参数被传递,返回新的组件将会订阅Redux的store的更新(update)。这意味着任何时刻store更新,mapStateToProps将会被调用mapStateToProps必须返回一个纯对象(plain object),这个对象将会合并进组件的属性(props)。如果你不想订阅store的更新,可以给mapStateToProps参数传递null或者undefined

如果你的mapStateToProps函数被声明接受两个参数,mapStateToProps调用时第一个参数是store state,传递给连接组件(connected component)的属性将会被作为第二个参数。如果连接组件接受到新的props(浅比较),mapStateToProps也会再次调用

注意: 在一些更高级的情况下,你需要更好的控制渲染的性能mapStateToProps可以返回一个函数。这种场景下,返回的函数将会被作为特定组件实例的mapStateProps()函数。这允许你可以对每个实例缓存。但大部分应用用不到。

mapStateToProps函数接受一个参数: Redux中store的state,并返回一个对象作为属性返回给被包裹的组件。这通常被称为`selector。

  • mapDispatchToProps(dispatch,[ownProps]): dispatchProps:

如果传入参数是一个对象,对象中的每个函数都被认为是Redux的action creator函数。返回的对象中的每个action creator函数都会被dispatch所包裹,因此可以直接调用,最终会被合并进入组件的属性

如果传递一个函数,该函数的第一个参数为dispatch。需要你返回一个对象,其中的属性以你的方式将dispatch与action creator相绑定。

如果你的mapDispatchToProps函数声明接受两个参数,第一个函数dispatch,第二个参数是传递给连接组件的属性。每当连接组件收到新的参数时,mapDispatchToProps就会被再次调用

如果没有传入自定义mapDispatchToProps函数或者对象,默认的mapDispatchToProps将为你的组件注入dispatch属性

注意: mapDispatchToProps也可以返回函数用法mapStateToProps相同

  • mergeProps(stateProps,dispatchProps,ownProps): props:

如果指定了这个参数,传入的参数为:函数 mapStateToProps()mapDispatchToProps()的运行结果以及传入连接组件的属性。从该函数返回的对象将会被当做属性传递给被包裹的组件。你可能会指定这个函数来基于props来选择性传入state,或者按照传入props绑定action creator。如果你省略了这个函数,默认是实现方式是:`
Object.assign({},ownProps,stateProps,dispatchProps)
`

  • options
    如果你指定了这个选项,更进一步自定义connector的行为。除了可以传入connectAdvanced的选项,还可以接受额外的选项:

    • [pure] (Boolean): 如果参数为true,用来避免重新渲染并调用mapStateToPropsmapDispatchToPropsmergeProps时基于各自的等值比较函数来比较所涉及到的stateprops对象。

    • [areStatesEqual] (Function): 如果参数puretrue,用来比较传入的store与之前的store值。默认值: strictEqual (===)。

    • [areOwnPropsEqual] (Function):如果参数puretrue,用来比较传入的props与之前的props值。默认值: strictEqual (===)。

    • [areStatePropsEqual] (Function):如果参数puretrue,用以比较mapStateToProps函数的结果与之前的结果值。

    • [areMergedPropsEqual] (Function): 如果参数puretrue,比较mergeProps函数的结果与之前的值。默认值为:shallowEqual。

    • [storeKey] (String): 用以从context获取store的key值。你仅仅可能在有多个store值的情况下才需要这个选项,默认值为:store

connect源码

  connect代码如下:

export function createConnect({
  connectHOC = connectAdvanced,mapStateToPropsFactories = defaultMapStateToPropsFactories,mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,mergePropsFactories = defaultMergePropsFactories,selectorFactory = defaultSelectorFactory
} = {}) {
  return function connect(
    mapStateToProps,mapDispatchToProps,mergeProps,{
      pure = true,areStatesEqual = strictEqual,areOwnPropsEqual = shallowEqual,areStatePropsEqual = shallowEqual,areMergedPropsEqual = shallowEqual,...extraOptions
    } = {}
  ) {
    const initMapStateToProps = match(mapStateToProps,mapStateToPropsFactories,'mapStateToProps')
    const initMapDispatchToProps = match(mapDispatchToProps,mapDispatchToPropsFactories,'mapDispatchToProps')
    const initMergeProps = match(mergeProps,mergePropsFactories,'mergeProps')
    return connectHOC(selectorFactory,{
      methodName: 'connect',getDisplayName: name => `Connect(${name})`,shouldHandleStateChanges: Boolean(mapStateToProps),initMapStateToProps,initMapDispatchToProps,initMergeProps,pure,areStatesEqual,areOwnPropsEqual,areStatePropsEqual,areMergedPropsEqual,...extraOptions
    })
  }
}
const connect = createConnect();

  createConnect作为高阶函数,返回connect函数,通过柯里化的方式首先接受以下参数: connectHOCmapStateToPropsFactoriesmapDispatchToPropsFactoriesmergePropsFactoriesselectorFactory
 

connectHOC

  传入用来生成连接到store的高阶组件(HOC),默认是之前介绍过的connectAdvanced
  

selectorFactory

  selectorFactory用来生成selector,第一个参数将传入connectAdvanced。我们知道传入connectAdvancedselectorFactory函数主要是初始化selector函数。selector函数在每次connector component需要计算新的props都会被调用,selector函数会返回纯对象(plain object),这个对象会作为props传递给被包裹的组件(WrappedComponent)。selectorFactory函数签名为:

selectorFactory(dispatch,factoryOptions): selector(state,ownProps): props (Function)

  我们来看看reduxselectorFactory是怎么定义的:
 

const selectorFactory = finalPropsSelectorFactory(dispatch,{
  initMapStateToProps,...options
}) {
  const mapStateToProps = initMapStateToProps(dispatch,options)
  const mapDispatchToProps = initMapDispatchToProps(dispatch,options)
  const mergeProps = initMergeProps(dispatch,options)

  if (process.env.NODE_ENV !== 'production') {
    verifySubselectors(mapStateToProps,options.displayName)
  }

  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

  return selectorFactory(
    mapStateToProps,dispatch,options
  )
}

  selectorFactory函数首先接受两个参数,dispatch和一系列的factoryOptions,通过一系列的初始化函数分别生成mapStateToPropsmapDispatchToPropsmergeProps(初始化函数随后会详细介绍)。然后会在非生产环境下对上述三个函数进行验证(验证主要涉及到函数是否为空函数中是否有dependsOnOwnProps属性,这个属性随后会介绍的)。随后便是函数的重点部分,根据options.pure是否为true,选择恰当的selectorFactory,然后返回selectorFactory(...args)
  当options.purefalse时,selectorFactory的值为:impureFinalPropsSelectorFactory:

function impureFinalPropsSelectorFactory(
  mapStateToProps,dispatch
) {
  return function impureFinalPropsSelector(state,ownProps) {
    return mergeProps(
      mapStateToProps(state,ownProps),mapDispatchToProps(dispatch,ownProps
    )
  }
}

  我们知道,selectorFactory会返回selector函数,返回的函数会接受两个参数:stateownProps并最终返回属性传递给被包裹的组件。我们发现impureFinalPropsSelectorFactory非常的简单,只是单纯的将要求的参数传递给mapStateToPropsmapDispatchToProps,并将其结果连同ownProps一起传递给mergeProps,并将最后mergeProps的结果作为selector函数的结果。这个结果最终会传递给被包裹组件,这个函数没有什么难度而且非常符合connect函数的API。
  但我们知道在默认情况下,options.puretrue。因此selectorFactory的值为:pureFinalPropsSelectorFactory:

pureFinalPropsSelectorFactory(
  mapStateToProps,{ areStatesEqual,areStatePropsEqual }
) {
  let hasRunAtLeastOnce = false
  let state
  let ownProps
  let stateProps
  let dispatchProps
  let mergedProps

  // ......    
  return function pureFinalPropsSelector(nextState,nextOwnProps) {
    return hasRunAtLeastOnce
      ? handleSubsequentCalls(nextState,nextOwnProps)
      : handleFirstCall(nextState,nextOwnProps)
  }
}

  函数pureFinalPropsSelectorFactory中有一个闭包变量hasRunAtLeastOnce用来判断是否是第一次调用,如果selector函数是第一次调用selector会返回handleFirstCall(nextState,nextOwnProps)否则返回handleSubsequentCalls(nextState,nextOwnProps)

function handleFirstCall(firstState,firstOwnProps) {
    state = firstState
    ownProps = firstOwnProps
    stateProps = mapStateToProps(state,ownProps)
    dispatchProps = mapDispatchToProps(dispatch,ownProps)
    mergedProps = mergeProps(stateProps,ownProps)
    hasRunAtLeastOnce = true
    return mergedProps
}

  handleFirstCall与之前的impureFinalPropsSelector相比,只是做了缓存,保存了stateownProps以及mapStateToPropsdispatchPropsmergedProps的结果值。

function handleSubsequentCalls(nextState,nextOwnProps) {
    const propsChanged = !areOwnPropsEqual(nextOwnProps,ownProps)
    const stateChanged = !areStatesEqual(nextState,state)
    state = nextState
    ownProps = nextOwnProps

    if (propsChanged && stateChanged) return handleNewPropsAndNewState()
    if (propsChanged) return handleNewProps()
    if (stateChanged) return handleNewState()
    return mergedProps
}

  再看函数handleSubsequentCalls。其中areOwnPropsEqualareStatesEqual分别用来判断props和state现在的值与缓存的值是否相等函数handleSubsequentCalls首先判断state、props的前后值是否有变化,然后缓存了stateownProps。如果props和state都发送改变了,返回handleNewPropsAndNewState的结果,如果props改变了,返回handleNewProps的运行结果。如果state改变,返回handleNewState运行结果,否则如果stateprops都没发生改变,说明都没有发生改变。直接返回之前缓存的mergedProps的值。
  
  handleNewPropsAndNewState定义如下:

function handleNewPropsAndNewState() {
    stateProps = mapStateToProps(state,ownProps)

    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch,ownProps)

    mergedProps = mergeProps(stateProps,ownProps)
    return mergedProps
}

  我们看到,如果props和state都发送改变了,调用handleNewPropsAndNewState,首先就是运行
mapStateToProps返回stateProps的值并缓存,其次我们会根据mapDispatchToProps.dependsOnOwnProps的值去判别是否运行mapDispatchToPropsdependsOnOwnProps的值主要是用来判别mapDispatchToProps是否依赖于ownProps的值。最终执行mergeProps函数,缓存结果并传入被包裹的组件。

function handleNewProps() {
    if (mapStateToProps.dependsOnOwnProps)
      stateProps = mapStateToProps(state,ownProps)
    return mergedProps
}

  理解了handleNewPropsAndNewStatehandleNewProps将会非常简单,分别去判别statedispatchProps是否与ownProps相关。以判别是否需要重新运行mapStateToPropsmapDispatchToProps。最终将mergeProps运行的值缓存并传递给被包裹的组件。

function handleNewState() {
    const nextStateProps = mapStateToProps(state,ownProps)
    const statePropsChanged = !areStatePropsEqual(nextStateProps,stateProps)
    stateProps = nextStateProps
    if (statePropsChanged)
      mergedProps = mergeProps(stateProps,ownProps)
      
    return mergedProps
}

  handleNewState用来生成新的state。根据是否state变化,选择性是否执行mergeProps,最终返回mergedProps给被包裹组件。
  
  到现在为止,其实我们已经知道了selectorFactory是与pure值挂钩的。如果puretrue的话,selectorFactory返回的selector会对stateprops等值都会缓存,然后会根据具体的场景,尽可能使得传入被包裹组件的值改动最少(即尽可能返回相同的值),其目的就是减少不必要的渲染。当purefalse值,不会做任何的缓存。
  

mapStateToProps起源

  看完了selectorFactory,我们需要去了解一下mapStateToProps是怎么来的:

//connect.js
// initMapStateToProps会被传入 selectorFactory
const initMapStateToProps = match(mapStateToProps,'mapStateToProps')

  

//selectorFactory.js
const mapStateToProps = initMapStateToProps(dispatch,options)
//mapStateToProps的使用(注意这里的mapStateToProps不是传入的函数,而是init函数生成函数):
const stateProps = mapStateToProps(state,ownProps)

  我们可以看到,首先在connect.js中通过match函数生成initMapStateToProps。然后在selectorFactory中,生成mapStateToProps函数,然后会在selector函数中使用mapStateToProps生成stateProps,最后将stateProps传递给被包裹的组件。

  首先看match函数的定义:

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}.`)
  }
}

  接下来的内容相对来说会比较复杂,我们先提前梳理一下match函数的运作,其中factories是一个数组,它的实参将会是类似于mapStateToPropsFactories(数组)等值,然后args将是你自定义mapStateToProps函数等值(比如mapStateToDispatch)。我们将会以args作为参数从后到前执行factories数组中的每一个函数,找到第一个返回不为假(类似于undefined)的函数并且我们可以保证这个函数返回的是另一个函数,其签名类似于:

(dispatch,options)=>{
    //....
    return ()=>{
    }
}

这个返回的函数接受dispatch和其他选项options作为参数,最终返回一个函数selector使用的函数 ,比如mapStateToPropsFactories一定会返回一个类似与于下面的函数:

(state,ownProps) =>{
    //......
    //return plain object
}

这个函数将用来计算新的state传递给被包裹的组件。

  对于mapStateToProps的来源要追溯到:

const initMapStateToProps = match(mapStateToProps,'mapStateToProps')

  
  在函数match中第一个实参是你传入connectmapStateToProps。第二个实参mapStateToPropsFactories的定义如下:

const mapStateToPropsFactories = [
  whenMapStateToPropsIsFunction,whenMapStateToPropsIsMissing
];

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

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

  上面的代码都不难,首先判断传入的mapStateToProps是不是类似于null,如果是执行whenMapStateToPropsIsMissing否则去执行whenMapStateToPropsIsFunction。对于whenMapStateToPropsIsMissing来说,重要的是whenMapStateToPropsIsMissing的定义:

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

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

  wrapMapToPropsConstant函数接受的参数是一个函数,这个函数负责在selector返回一个常量作为props返回给被包裹组件。因为返回的总是一个常量,所以dependsOnOwnPropsfalse,表示返回给被包裹组件的值与连接到store的高阶组件接受到的props无关。
  
  那么whenMapStateToPropsIsMissing函数调用wrapMapToPropsConstant的参数是一个空函数(()=>{}),那就说明在mapStateToProps值为空(null)的时候,是不给被包裹组件传递任何的属性的。
  
  whenMapStateToPropsIsFunction的情况会比较复杂,如果传入的mapStateToProps是一个函数,那么就会调用wrapMapToPropsFunc:
  

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 = true

    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch,ownProps) {
      proxy.mapToProps = mapToProps
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      let props = proxy(stateOrDispatch,ownProps)

      if (typeof props === 'function') {
        proxy.mapToProps = props
        proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
        props = proxy(stateOrDispatch,ownProps)
      }

      if (process.env.NODE_ENV !== 'production') 
        verifyPlainObject(props,displayName,methodName)

      return props
    }

    return proxy
  }
}

  wrapMapToPropsFunc函数相对来说比较复杂,接受的参数是你传入的mapStateToProps函数(methodName的作用只是错误提示),返回的是初始化selector函数(initProxySelector)。当使用initProxySelector初始化selector的时候,返回的函数proxy实则为一个代理(proxy)。第一次执行proxy(selector)时,dependsOnOwnProps的值为true,所以相当于执行proxy.mapToProps(stateOrDispatch,ownProps)(detectFactoryAndVerify),然后将proxy.mapToProps属性设置为你所传入的mapStateToProps函数。这时候再去执行getDependsOnOwnProps的目的是去确定你传入的mapStateToProps是否需要传入props。然后再去执行proxy(stateOrDispatch,ownProps),这时候proxy.mapToProps已经不是之前的detectFactoryAndVerify而是你传入的mapStateToProps(所以不会出现死循环)。执行的结果就是mapStateToProps运行后的结果。如果prop是对象,将会直接传递给被包裹组件。但是我们之前讲过,mapStateToProps是可以返回一个函数的,如果返回的值为一个函数,这个函数将会被作为proxymapStateToProps,再次去执行proxy
  

mapDispatchToProps起源

  
  再去了解一下mapStateToProps的来源:

//connect.js
const initMapDispatchToProps = match(mapDispatchToProps,'mapDispatchToProps')
//selectFactory
const mapDispatchToProps = initMapDispatchToProps(dispatch,options)
//使用:
const dispatchProps = mapDispatchToProps(dispatch,ownProps)

  其实mapDispatchToProps是和mapStateToProps的来源非常相似,照理看mapDispatchToPropsFactories:

const mapDispatchToPropsFactories =  [
  whenMapDispatchToPropsIsFunction,whenMapDispatchToPropsIsMissing,whenMapDispatchToPropsIsObject
]

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

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

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

  如果你已经看懂了wrapMapToPropsConstantwrapMapToPropsFunc函数的话,mapDispatchToPropsFactories也就不难了。如果传入的mapStateToProps的值是一个对象的话,会调用whenMapDispatchToPropsIsObject。继而调用wrapMapToPropsConstant并传入的参数是函数:dispatch => bindActionCreators(mapDispatchToProps,dispatch)。根据我们之前经验,那么传递给被包裹的组件的属性将是:bindActionCreators(mapDispatchToProps,dispatch)的运行结果,即被dispatch包裹的action

  如果没有传入mapDispatchToProps函数的话,调用whenMapDispatchToPropsIsMissing。传入函数wrapMapToPropsConstant的参数为:dispatch => ({ dispatch }),那么被包裹的组件接受的参数即是storedispatch方法
  
  如果传入的mapDispatchToProps是一个函数调用whenMapDispatchToPropsIsFunction函数。从而调用wrapMapToPropsFunc(mapDispatchToProps,'mapDispatchToProps')。运行的原理与运行wrapMapToPropsFunc(mapStateToProps,'mapStateToProps')基本相同,可以参照之前。
  

mergeProps起源

//connect.js
const initMergeProps = match(mergeProps,'mergeProps')
//selectorFactory
const mergeProps = initMergeProps(dispatch,options)
//使用
mergedProps = mergeProps(stateProps,ownProps)

  
  还是先看一下mergePropsFactories是怎么定义的:
  

const mergePropsFactories = [
  whenMergePropsIsFunction,whenMergePropsIsOmitted
]

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

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

  如果你没有传入mapStateToProps函数,那么调用函数whenMergePropsIsOmitted()。到最后margedProps函数即是defaultMergeProps,defaultMergeProps的定义为:

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

  如果你传入了mapStateToProps函数调用函数whenMergePropsIsFunction()调用wrapMergePropsFunc(mergeProps),其中参数mergeProps即是你所传入的mergeProps:

function wrapMergePropsFunc(mergeProps) {
  return function initMergePropsProxy(dispatch,{ displayName,areMergedPropsEqual }) {
    let hasRunOnce = false
    let mergedProps

    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
    }
  }
}

  wrapMergePropsFunc中涉及到性能优化,首先wrapMergePropsFunc返回一个初始mergeProps函数(mergePropsProxy)。函数mergePropsProxy闭包一个变量hasRunOnce来记录mergeProps运行次数,在mergeProps第一次运行时,会保存第一次传入被包裹组件的的props,再以后的运行过程中,如果你传入的参数puretrue并且前后的mergedProps值不同时(比较函数你可以自定义)才会传入新的属性,否则将传入之前的缓存值,以此来优化不必要的渲染。

  到此为止,我们基本已经在代码层面讲完了connect函数的原理,文章很长,有的地方可能相对比较难理解,建议大家都可以去从整体上看看react-redux的源码。react-redux源码解读系列接下来会以其他的角度去分析react-redux,欢迎大家继续关注。

原文链接:https://www.f2er.com/react/303415.html

猜你在找的React相关文章