connect API
上篇文章庖丁解牛React-Redux(一): connectAdvanced介绍了react-redux的Provider
、connectAdvanced
几个重要API的原理,其中connectAdvanced
是connect
函数的基础,这篇文章将主要介绍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
属性。
mergeProps(stateProps,dispatchProps,ownProps): props:
如果指定了这个参数,传入的参数为:函数 mapStateToProps()
、mapDispatchToProps()
的运行结果以及传入连接组件的属性。从该函数返回的对象将会被当做属性传递给被包裹的组件。你可能会指定这个函数来基于props来选择性传入state,或者按照传入props绑定action creator。如果你省略了这个函数,默认是实现方式是:`
Object.assign({},ownProps,stateProps,dispatchProps)
`
-
options
如果你指定了这个选项,更进一步自定义connector的行为。除了可以传入connectAdvanced
的选项,还可以接受额外的选项:[pure] (Boolean): 如果参数为true,用来避免重新渲染并调用
mapStateToProps
、mapDispatchToProps
和mergeProps
时基于各自的等值比较函数来比较所涉及到的state
和props
对象。[areStatesEqual] (Function): 如果参数
pure
为true
,用来比较传入的store与之前的store值。默认值: strictEqual (===)。[areOwnPropsEqual] (Function):如果参数
pure
为true
,用来比较传入的props与之前的props值。默认值: strictEqual (===)。[areStatePropsEqual] (Function):如果参数
pure
为true
,用以比较mapStateToProps
函数的结果与之前的结果值。[areMergedPropsEqual] (Function): 如果参数
pure
为true
,比较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
函数,通过柯里化的方式首先接受以下参数: connectHOC
、mapStateToPropsFactories
、mapDispatchToPropsFactories
、mergePropsFactories
与selectorFactory
。
connectHOC
传入用来生成连接到store的高阶组件(HOC),默认是之前介绍过的connectAdvanced
。
selectorFactory
selectorFactory
用来生成selector
,第一个参数将传入connectAdvanced
。我们知道传入connectAdvanced
的selectorFactory
函数主要是初始化selector函数。selector函数在每次connector component需要计算新的props都会被调用,selector函数会返回纯对象(plain object),这个对象会作为props传递给被包裹的组件(WrappedComponent)。selectorFactory
的函数签名为:
selectorFactory(dispatch,factoryOptions): selector(state,ownProps): props (Function)
我们来看看redux
的selectorFactory
是怎么定义的:
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
,通过一系列的初始化函数分别生成了mapStateToProps
、mapDispatchToProps
、mergeProps
(初始化函数随后会详细介绍)。然后会在非生产环境下对上述三个函数进行验证(验证主要涉及到该函数是否为空和函数中是否有dependsOnOwnProps属性,这个属性随后会介绍的)。随后便是函数的重点部分,根据options.pure
是否为true,选择恰当的selectorFactory
,然后返回selectorFactory(...args)
。
当options.pure
为false
时,selectorFactory
的值为:impureFinalPropsSelectorFactory
:
function impureFinalPropsSelectorFactory( mapStateToProps,dispatch ) { return function impureFinalPropsSelector(state,ownProps) { return mergeProps( mapStateToProps(state,ownProps),mapDispatchToProps(dispatch,ownProps ) } }
我们知道,selectorFactory
会返回selector
函数,返回的函数会接受两个参数:state
与ownProps
并最终返回属性传递给被包裹的组件。我们发现impureFinalPropsSelectorFactory
非常的简单,只是单纯的将要求的参数传递给mapStateToProps
,mapDispatchToProps
,并将其结果连同ownProps
一起传递给mergeProps
,并将最后mergeProps
的结果作为selector
函数的结果。这个结果最终会传递给被包裹组件,这个函数没有什么难度而且非常符合connect
函数的API。
但我们知道在默认情况下,options.pure
为true
。因此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
相比,只是做了缓存,保存了state
、ownProps
以及mapStateToProps
、dispatchProps
和mergedProps
的结果值。
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
。其中areOwnPropsEqual
、areStatesEqual
分别用来判断props和state现在的值与缓存的值是否相等函数。handleSubsequentCalls
首先判断state、props的前后值是否有变化,然后缓存了state
、ownProps
。如果props和state都发送改变了,返回handleNewPropsAndNewState
的结果,如果props
改变了,返回handleNewProps
的运行结果。如果state
改变,返回handleNewState
运行结果,否则如果state
和props
都没发生改变,说明都没有发生改变。直接返回之前缓存的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
的值去判别是否运行mapDispatchToProps
。dependsOnOwnProps
的值主要是用来判别mapDispatchToProps
是否依赖于ownProps的值。最终执行mergeProps
函数,缓存结果并传入被包裹的组件。
function handleNewProps() { if (mapStateToProps.dependsOnOwnProps) stateProps = mapStateToProps(state,ownProps) return mergedProps }
理解了handleNewPropsAndNewState
,handleNewProps
将会非常简单,分别去判别state
与dispatchProps
是否与ownProps相关。以判别是否需要重新运行mapStateToProps
和mapDispatchToProps
。最终将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
值挂钩的。如果pure
为true
的话,selectorFactory
返回的selector
会对state
和props
等值都会缓存,然后会根据具体的场景,尽可能使得传入被包裹组件的值改动最少(即尽可能返回相同的值),其目的就是减少不必要的渲染。当pure
为false
值,不会做任何的缓存。
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
中第一个实参是你传入connect
的mapStateToProps
。第二个实参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返回给被包裹组件。因为返回的总是一个常量,所以dependsOnOwnProps
为false
,表示返回给被包裹组件的值与连接到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
是可以返回一个函数的,如果返回的值为一个函数,这个函数将会被作为proxy
的mapStateToProps
,再次去执行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 }
如果你已经看懂了wrapMapToPropsConstant
和wrapMapToPropsFunc
的函数的话,mapDispatchToPropsFactories
也就不难了。如果传入的mapStateToProps
的值是一个对象的话,会调用whenMapDispatchToPropsIsObject
。继而调用了wrapMapToPropsConstant
并传入的参数是函数:dispatch => bindActionCreators(mapDispatchToProps,dispatch)
。根据我们之前经验,那么传递给被包裹的组件的属性将是:bindActionCreators(mapDispatchToProps,dispatch)
的运行结果,即被dispatch
包裹的action
。
如果没有传入mapDispatchToProps
函数的话,调用whenMapDispatchToPropsIsMissing
。传入函数wrapMapToPropsConstant
的参数为:dispatch => ({ dispatch })
,那么被包裹的组件接受的参数即是store
的dispatch
方法。
如果传入的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
,再以后的运行过程中,如果你传入的参数pure
为true
并且前后的mergedProps
值不同时(比较函数你可以自定义)才会传入新的属性,否则将传入之前的缓存值,以此来优化不必要的渲染。
到此为止,我们基本已经在代码层面讲完了connect
函数的原理,文章很长,有的地方可能相对比较难理解,建议大家都可以去从整体上看看react-redux
的源码。react-redux
源码解读系列接下来会以其他的角度去分析react-redux
,欢迎大家继续关注。