You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionmatch(arg,factories,name){for(leti=factories.length-1;i>=0;i--){constresult=factories[i](arg)if(result)returnresult}return(dispatch,options)=>{thrownewError(`Invalid value of type ${typeofarg} for ${name} argument when connecting component ${options.wrappedComponentName}.`)}}
connect API
上篇文章庖丁解牛React-Redux(一): connectAdvanced介绍了react-redux的
Provider
、connectAdvanced
几个重要API的原理,其中connectAdvanced
是connect
函数的基础,这篇文章将主要介绍connect
函数的原理。之前没有阅读过connectAdvanced
最好提前阅读一下这篇文章。之前的文章有读者反映看起来比较晦涩,所以我准备随后会出一篇关于类似图解connectAdvanced
的文章,不讲代码,主要从原理的方面诠释connectAdvanced
。再次做个广告,欢迎大家关注我的掘金账号和我的博客。最开始我们还是来介绍一下
connect
函数:将React组件连接到Redux store,
connect
函数是connectAdvanced
的正面,为大多数常见场景提供了易于使用的API。connect
函数并不会修改传递的组件,相反,它会返回一个新的,连接到store
的组件类。参数:
如果这个参数被传递,返回新的组件将会订阅Redux的store的更新(update)。这意味着任何时刻
store
更新,mapStateToProps
将会被调用。mapStateToProps
必须返回一个纯对象(plain object),这个对象将会合并进组件的属性(props)。如果你不想订阅store的更新,可以给mapStateToProps
参数传递null
或者undefined
。如果你的
mapStateToProps
函数被声明接受两个参数,mapStateToProps
在调用时第一个参数是store state
,传递给连接组件(connected component)的属性将会被作为第二个参数。如果连接组件接受到新的props(浅比较),mapStateToProps
也会再次调用。如果传入参数是一个对象,对象中的每个函数都被认为是Redux的action creator函数。返回的对象中的每个action creator函数都会被
dispatch
所包裹,因此可以直接调用,最终会被合并进入组件的属性。如果传递一个函数,该函数的第一个参数为
dispatch
。需要你返回一个对象,其中的属性以你的方式将dispatch
与action creator相绑定。如果你的
mapDispatchToProps
函数声明接受两个参数,第一个函数是dispatch
,第二个参数是传递给连接组件的属性。每当连接组件收到新的参数时,mapDispatchToProps
就会被再次调用。如果没有传入自定义的
mapDispatchToProps
函数或者对象,默认的mapDispatchToProps
将为你的组件注入dispatch
属性。如果指定了这个参数,传入的参数为:函数
mapStateToProps()
、mapDispatchToProps()
的运行结果以及传入连接组件的属性。从该函数返回的对象将会被当做属性传递给被包裹的组件。你可能会指定这个函数来基于props来选择性传入state,或者按照传入props绑定action creator。如果你省略了这个函数,默认是实现方式是:Object.assign({}, ownProps, stateProps, dispatchProps)
如果你指定了这个选项,更进一步自定义connector的行为。除了可以传入
connectAdvanced
的选项,还可以接受额外的选项:mapStateToProps
、mapDispatchToProps
和mergeProps
时基于各自的等值比较函数来比较所涉及到的state
和props
对象。pure
为true
,用来比较传入的store与之前的store值。默认值: strictEqual (===)。pure
为true
,用来比较传入的props与之前的props值。默认值: strictEqual (===)。pure
为true
,用以比较mapStateToProps
函数的结果与之前的结果值。pure
为true
,比较mergeProps
函数的结果与之前的值。默认值为:shallowEqual。store
。connect源码
connect
的代码如下: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
的函数签名为:我们来看看
redux
的selectorFactory
是怎么定义的:selectorFactory
函数首先接受两个参数,dispatch
和一系列的factoryOptions
,通过一系列的初始化函数分别生成了mapStateToProps
、mapDispatchToProps
、mergeProps
(初始化函数随后会详细介绍)。然后会在非生产环境下对上述三个函数进行验证(验证主要涉及到该函数是否为空和函数中是否有dependsOnOwnProps属性,这个属性随后会介绍的)。随后便是函数的重点部分,根据options.pure
是否为true,选择恰当的selectorFactory
,然后返回selectorFactory(...args)
。当
options.pure
为false
时,selectorFactory
的值为:impureFinalPropsSelectorFactory
:我们知道,
selectorFactory
会返回selector
函数,返回的函数会接受两个参数:state
与ownProps
并最终返回属性传递给被包裹的组件。我们发现impureFinalPropsSelectorFactory
非常的简单,只是单纯的将要求的参数传递给mapStateToProps
,mapDispatchToProps
,并将其结果连同ownProps
一起传递给mergeProps
,并将最后mergeProps
的结果作为selector
函数的结果。这个结果最终会传递给被包裹组件,这个函数没有什么难度而且非常符合connect
函数的API。但我们知道在默认情况下,
options.pure
为true
。因此selectorFactory
的值为:pureFinalPropsSelectorFactory
:函数
pureFinalPropsSelectorFactory
中有一个闭包变量hasRunAtLeastOnce
用来判断是否是第一次调用,如果selector
函数是第一次调用,selector
会返回handleFirstCall(nextState, nextOwnProps)
否则返回handleSubsequentCalls(nextState, nextOwnProps)
。handleFirstCall
与之前的impureFinalPropsSelector
相比,只是做了缓存,保存了state
、ownProps
以及mapStateToProps
、dispatchProps
和mergedProps
的结果值。再看函数
handleSubsequentCalls
。其中areOwnPropsEqual
、areStatesEqual
分别用来判断props和state现在的值与缓存的值是否相等函数。handleSubsequentCalls
首先判断state、props的前后值是否有变化,然后缓存了state
、ownProps
。如果props和state都发送改变了,返回handleNewPropsAndNewState
的结果,如果props
改变了,返回handleNewProps
的运行结果。如果state
改变,返回handleNewState
运行结果,否则如果state
和props
都没发生改变,说明都没有发生改变。直接返回之前缓存的mergedProps
的值。handleNewPropsAndNewState
定义如下:我们看到,如果props和state都发送改变了,调用了
handleNewPropsAndNewState
,首先就是运行mapStateToProps
返回stateProps
的值并缓存,其次我们会根据mapDispatchToProps.dependsOnOwnProps
的值去判别是否运行mapDispatchToProps
。dependsOnOwnProps
的值主要是用来判别mapDispatchToProps
是否依赖于ownProps的值。最终执行mergeProps
函数,缓存结果并传入被包裹的组件。理解了
handleNewPropsAndNewState
,handleNewProps
将会非常简单,分别去判别state
与dispatchProps
是否与ownProps相关。以判别是否需要重新运行mapStateToProps
和mapDispatchToProps
。最终将mergeProps
运行的值缓存并传递给被包裹的组件。handleNewState
用来生成新的state。根据是否state
变化,选择性是否执行mergeProps
,最终返回mergedProps
给被包裹组件。到现在为止,其实我们已经知道了
selectorFactory
是与pure
值挂钩的。如果pure
为true
的话,selectorFactory
返回的selector
会对state
和props
等值都会缓存,然后会根据具体的场景,尽可能使得传入被包裹组件的值改动最少(即尽可能返回相同的值),其目的就是减少不必要的渲染。当pure
为false
值,不会做任何的缓存。mapStateToProps起源
看完了
selectorFactory
,我们需要去了解一下mapStateToProps
是怎么来的:我们可以看到,首先在
connect.js
中通过match
函数取生成initMapStateToProps
。然后在selectorFactory
中,生成了mapStateToProps
的函数,然后会在selector
函数中使用mapStateToProps
生成了stateProps
,最后将stateProps
传递给被包裹的组件。首先看
match
函数的定义:接下来的内容相对来说会比较复杂,我们先提前梳理一下
match
函数的运作,其中factories
是一个数组,它的实参将会是类似于mapStateToPropsFactories
(数组)等值,然后args
将是你自定义的mapStateToProps
函数等值(比如mapStateToDispatch
)。我们将会以args
作为参数从后到前执行factories
数组中的每一个函数,找到第一个返回不为假(类似于undefined
)的函数并且我们可以保证这个函数返回的是另一个函数,其签名类似于:这个返回的函数接受
dispatch
和其他选项options
作为参数,最终返回一个函数供selector
使用的函数 ,比如mapStateToPropsFactories
一定会返回一个类似与于下面的函数:这个函数将用来计算新的state传递给被包裹的组件。
对于
mapStateToProps
的来源要追溯到:在函数
match
中第一个实参是你传入connect
的mapStateToProps
。第二个实参mapStateToPropsFactories
的定义如下:上面的代码都不难,首先判断传入的
mapStateToProps
是不是类似于null
,如果是执行whenMapStateToPropsIsMissing
否则去执行whenMapStateToPropsIsFunction
。对于whenMapStateToPropsIsMissing
来说,重要的是whenMapStateToPropsIsMissing
的定义:wrapMapToPropsConstant
函数接受的参数是一个函数,这个函数负责在selector
返回一个常量作为props返回给被包裹组件。因为返回的总是一个常量,所以dependsOnOwnProps
为false
,表示返回给被包裹组件的值与连接到store的高阶组件接受到的props
无关。那么
whenMapStateToPropsIsMissing
函数调用wrapMapToPropsConstant
的参数是一个空函数(()=>{}
),那就说明在mapStateToProps
值为空(null
)的时候,是不给被包裹组件传递任何的属性的。whenMapStateToPropsIsFunction
的情况会比较复杂,如果传入的mapStateToProps
是一个函数,那么就会调用wrapMapToPropsFunc
: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
的来源:其实
mapDispatchToProps
是和mapStateToProps
的来源非常相似,照理看mapDispatchToPropsFactories
:如果你已经看懂了
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起源
还是先看一下
mergePropsFactories
是怎么定义的:如果你没有传入
mapStateToProps
函数,那么调用函数whenMergePropsIsOmitted()
。到最后margedProps
函数即是defaultMergeProps
,defaultMergeProps
的定义为:如果你传入了
mapStateToProps
函数,调用函数whenMergePropsIsFunction()
,调用了wrapMergePropsFunc(mergeProps)
,其中参数mergeProps
即是你所传入的mergeProps
:wrapMergePropsFunc
中涉及到性能优化,首先wrapMergePropsFunc
返回一个初始mergeProps
的函数(mergePropsProxy
)。函数mergePropsProxy
闭包一个变量hasRunOnce
来记录mergeProps
运行次数,在mergeProps
第一次运行时,会保存第一次传入被包裹组件的的props
,再以后的运行过程中,如果你传入的参数pure
为true
并且前后的mergedProps
值不同时(比较函数你可以自定义)才会传入新的属性,否则将传入之前的缓存值,以此来优化不必要的渲染。到此为止,我们基本已经在代码层面讲完了
connect
函数的原理,文章很长,有的地方可能相对比较难理解,建议大家都可以去从整体上看看react-redux
的源码。react-redux
源码解读系列接下来会以其他的角度去分析react-redux
,欢迎大家继续关注。The text was updated successfully, but these errors were encountered: