上篇文章庖丁解牛React-Redux(一): connectAdvanced介绍了react-redux的Provider
、connectAdvanced
几个重要API的原理,其中connectAdvanced
是connect
函数的基础,这篇文章将主要介绍connect
函数的原理。以前没有阅读过connectAdvanced
最好提早阅读一下这篇文章。以前的文章有读者反映看起来比较晦涩,因此我准备随后会出一篇关于相似图解connectAdvanced
的文章,不讲代码,主要从原理的方面诠释connectAdvanced
。再次作个广告,欢迎你们关注个人掘金帐号和个人博客。javascript
最开始咱们仍是来介绍一下connect
函数: java
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])复制代码
将React组件链接到Redux store,connect
函数是connectAdvanced
的正面,为大多数常见场景提供了易于使用的API。connect
函数并不会修改传递的组件,相反,它会返回一个新的,链接到store
的组件类。react
参数:git
mapStateToProps(state, [ownProps]): stateProps:
若是这个参数被传递,返回新的组件将会订阅Redux的store的更新(update)。这意味着任什么时候刻store
更新,mapStateToProps
将会被调用。mapStateToProps
必须返回一个纯对象(plain object),这个对象将会合并进组件的属性(props)。若是你不想订阅store的更新,能够给mapStateToProps
参数传递null
或者undefined
。github
若是你的mapStateToProps
函数被声明接受两个参数,mapStateToProps
在调用时第一个参数是store state
,传递给链接组件(connected component)的属性将会被做为第二个参数。若是链接组件接受到新的props(浅比较),mapStateToProps
也会再次调用。redux
注意: 在一些更高级的状况下,你须要更好的控制渲染的性能,
mapStateToProps
能够返回一个函数。这种场景下,返回的函数将会被做为特定组件实例的mapStateProps()
函数。这容许你能够对每一个实例缓存。但大部分应用用不到。api
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
的选项,还能够接受额外的选项:
mapStateToProps
、mapDispatchToProps
和mergeProps
时基于各自的等值比较函数来比较所涉及到的state
和props
对象。pure
为true
,用来比较传入的store与以前的store值。默认值: strictEqual (===)。pure
为true
,用来比较传入的props与以前的props值。默认值: strictEqual (===)。pure
为true
,用以比较mapStateToProps
函数的结果与以前的结果值。pure
为true
,比较mergeProps
函数的结果与以前的值。默认值为:shallowEqual。store
。 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
。
传入用来生成链接到store的高阶组件(HOC),默认是以前介绍过的connectAdvanced
。
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,
initMapDispatchToProps,
initMergeProps,
...options
}) {
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
if (process.env.NODE_ENV !== 'production') {
verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps, options.displayName)
}
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}复制代码
selectorFactory
函数首先接受两个参数,dispatch
和一系列的factoryOptions
,经过一系列的初始化函数分别生成了mapStateToProps
、mapDispatchToProps
、mergeProps
(初始化函数随后会详细介绍)。而后会在非生产环境下对上述三个函数进行验证(验证主要涉及到该函数是否为空和函数中是否有dependsOnOwnProps属性,这个属性随后会介绍的)。随后即是函数的重点部分,根据options.pure
是否为true,选择恰当的selectorFactory
,而后返回selectorFactory(...args)
。
当options.pure
为false
时,selectorFactory
的值为:impureFinalPropsSelectorFactory
:
function impureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch ) {
return function impureFinalPropsSelector(state, ownProps) {
return mergeProps(
mapStateToProps(state, ownProps),
mapDispatchToProps(dispatch, ownProps),
ownProps
)
}
}复制代码
咱们知道,selectorFactory
会返回selector
函数,返回的函数会接受两个参数:state
与ownProps
并最终返回属性传递给被包裹的组件。咱们发现impureFinalPropsSelectorFactory
很是的简单,只是单纯的将要求的参数传递给mapStateToProps
,mapDispatchToProps
,并将其结果连同ownProps
一块儿传递给mergeProps
,并将最后mergeProps
的结果做为selector
函数的结果。这个结果最终会传递给被包裹组件,这个函数没有什么难度并且很是符合connect
函数的API。
但咱们知道在默认状况下,options.pure
为true
。所以selectorFactory
的值为:pureFinalPropsSelectorFactory
:
pureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
{ areStatesEqual, areOwnPropsEqual, 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, dispatchProps, 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, dispatchProps, ownProps)
return mergedProps
}复制代码
咱们看到,若是props和state都发送改变了,调用了handleNewPropsAndNewState
,首先就是运行mapStateToProps
返回stateProps
的值并缓存,其次咱们会根据mapDispatchToProps.dependsOnOwnProps
的值去判别是否运行mapDispatchToProps
。dependsOnOwnProps
的值主要是用来判别mapDispatchToProps
是否依赖于ownProps的值。最终执行mergeProps
函数,缓存结果并传入被包裹的组件。
function handleNewProps() {
if (mapStateToProps.dependsOnOwnProps)
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, 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, dispatchProps, ownProps)
return mergedProps
}复制代码
handleNewState
用来生成新的state。根据是否state
变化,选择性是否执行mergeProps
,最终返回mergedProps
给被包裹组件。
到如今为止,其实咱们已经知道了selectorFactory
是与pure
值挂钩的。若是pure
为true
的话,selectorFactory
返回的selector
会对state
和props
等值都会缓存,而后会根据具体的场景,尽量使得传入被包裹组件的值改动最少(即尽量返回相同的值),其目的就是减小没必要要的渲染。当pure
为false
值,不会作任何的缓存。
看完了selectorFactory
,咱们须要去了解一下mapStateToProps
是怎么来的:
//connect.js
// initMapStateToProps会被传入 selectorFactory
const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, '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, mapStateToPropsFactories, '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
。
再去了解一下mapStateToProps
的来源:
//connect.js
const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, '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')
基本相同,能够参照以前。
//connect.js
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')复制代码
//selectorFactory
const mergeProps = initMergeProps(dispatch, options)
//使用
mergedProps = mergeProps(stateProps, dispatchProps, 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, dispatchProps, ownProps) {
return { ...ownProps, ...stateProps, ...dispatchProps }
}复制代码
若是你传入了mapStateToProps
函数,调用函数whenMergePropsIsFunction()
,调用了wrapMergePropsFunc(mergeProps)
,其中参数mergeProps
便是你所传入的mergeProps
:
function wrapMergePropsFunc(mergeProps) {
return function initMergePropsProxy(dispatch, { displayName, pure, areMergedPropsEqual }) {
let hasRunOnce = false
let mergedProps
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
}
}
}复制代码
wrapMergePropsFunc
中涉及到性能优化,首先wrapMergePropsFunc
返回一个初始mergeProps
的函数(mergePropsProxy
)。函数mergePropsProxy
闭包一个变量hasRunOnce
来记录mergeProps
运行次数,在mergeProps
第一次运行时,会保存第一次传入被包裹组件的的props
,再之后的运行过程当中,若是你传入的参数pure
为true
而且先后的mergedProps
值不一样时(比较函数你能够自定义)才会传入新的属性,不然将传入以前的缓存值,以此来优化没必要要的渲染。
到此为止,咱们基本已经在代码层面讲完了connect
函数的原理,文章很长,有的地方可能相对比较难理解,建议你们均可以去从总体上看看react-redux
的源码。react-redux
源码解读系列接下来会以其余的角度去分析react-redux
,欢迎你们继续关注。