- 阅读react-redux源码 - 零
- 阅读react-redux源码 - 一
- 阅读react-redux源码(二) - createConnect、match函数的实现
- 阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories
mapStateToPropsFactories
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'export function whenMapStateToPropsIsFunction(mapStateToProps) {return typeof mapStateToProps === 'function'? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps'): undefined
}export function whenMapStateToPropsIsMissing(mapStateToProps) {return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
首先mapStateToPropsFactories是一个数组[whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
。
从名字中很轻易就能看出来数组中两个函数分别处理什么任务。从右往左分别处理了,mapStateToProps参数为假值和mapStateToProps传入的是函数的情况。
whenMapStateToPropsIsMissing:如果入参是假值,那么就返回默认的函数,否则返回undefined。这个undefined被match捕获之后,match会继续遍历factories的下一项,也就是whenMapStateToPropsIsFunction。
whenMapStateToPropsIsFunction:如果mapStateToProps入参是函数,那么就返回wrapMapToPropsFunc(mapStateToProps, ‘mapStateToProps’)的返回值,否则返回undefined。也就是说入参不是函数就会返回undefined。到这里mapStateToPropsFactories数组就结束了,如果最后返回的是undefined,那么match函数会返回一个抛出错误的函数,当执行这个函数的时候回抛出错误。
所以对于入参mapStateToProps来说,如果需要可以传入一个函数,用于筛选组件需要的store.state,否则可不传,但是不能传入非function类型的真值,例如:true,string、number、object之类的值,否则会报错。
wrapMapToPropsConstant 和 wrapMapToPropsFunc
import verifyPlainObject from '../utils/verifyPlainObject'export function wrapMapToPropsConstant(getConstant) {return function initConstantSelector(dispatch, options) {const constant = getConstant(dispatch, options)function constantSelector() {return constant}constantSelector.dependsOnOwnProps = falsereturn constantSelector}
}// dependsOnOwnProps is used by createMapToPropsProxy to determine whether to pass props as args
// to the mapToProps function being wrapped. It is also used by makePurePropsSelector to determine
// whether mapToProps needs to be invoked when props have changed.
//
// A length of one signals that mapToProps does not depend on props from the parent component.
// A length of zero is assumed to mean mapToProps is getting args via arguments or ...args and
// therefore not reporting its length accurately..// function mapStateToProps(state, ownProps) { }
export function getDependsOnOwnProps(mapToProps) {return mapToProps.dependsOnOwnProps !== null &&mapToProps.dependsOnOwnProps !== undefined? Boolean(mapToProps.dependsOnOwnProps): mapToProps.length !== 1
}// Used by whenMapStateToPropsIsFunction and whenMapDispatchToPropsIsFunction,
// this function wraps mapToProps in a proxy function which does several things:
//
// * Detects whether the mapToProps function being called depends on props, which
// is used by selectorFactory to decide if it should reinvoke on props changes.
//
// * On first call, handles mapToProps if returns another function, and treats that
// new function as the true mapToProps for subsequent calls.
//
// * On first call, verifies the first result is a plain object, in order to warn
// the developer that their mapToProps function is not returning a valid result.
//
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)}// allow detectFactoryAndVerify to get ownPropsproxy.dependsOnOwnProps = trueproxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch,ownProps) {proxy.mapToProps = mapToPropsproxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)let props = proxy(stateOrDispatch, ownProps)// function mapToStateWrap() { return function mapToState(state, ownProps) {} }if (typeof props === 'function') {proxy.mapToProps = propsproxy.dependsOnOwnProps = getDependsOnOwnProps(props)props = proxy(stateOrDispatch, ownProps)}if (process.env.NODE_ENV !== 'production')verifyPlainObject(props, displayName, methodName)return props}return proxy}
}
这两个包裹函数的目的则是为mapToProps函数增强一些功能,mapToPrps函数包括mapStateToProps和mapDispatchToProps。
wrapMapToPropsConstant:该函数会用一个返回常量的函数作为入参,并返回一个函数。被返回的函数的返回值也是一个函数,也就是说一共有三层,需要执行三次才能拿到结果,也就是入参函数返回的常量。
wrapMapToPropsFunc:这个函数比较复杂。它返回一个mapToProps的代理函数,代理函数增加了几个功能点:
- 判断mapStateToProps函数是否依赖组件props
- 首次调用处理mapStateToProps返回一个mapStateToProps函数的情况
- 首次调用验证mapStateToProps返回值是一个简单对象
首先定义一个proxy函数:
const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {return proxy.dependsOnOwnProps? proxy.mapToProps(stateOrDispatch, ownProps): proxy.mapToProps(stateOrDispatch)
}
这个函数会检查是否依赖组件原来的props如果依赖则传入,否则不传入。
默认mapToProps函数依赖组件被传入的props:
proxy.dependsOnOwnProps = true
这一段比较烧脑了,定义了proxy函数中首次真正执行的proxy.mapToProps函数:
proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch,ownProps
) {proxy.mapToProps = mapToProps proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)let props = proxy(stateOrDispatch, ownProps)// function mapToStateWrap() { return function mapToState(state, ownProps) {} }if (typeof props === 'function') {proxy.mapToProps = propsproxy.dependsOnOwnProps = getDependsOnOwnProps(props)props = proxy(stateOrDispatch, ownProps)}if (process.env.NODE_ENV !== 'production')verifyPlainObject(props, displayName, methodName)return props
}
首先这个函数是stateOrDispatch,而入参是stateOrDispatch和需要连接store的组件自身的props。
将mapToProps赋值给proxy.mapToProps,然后检查mapToProps是否依赖ownProps。然后执行 :
proxy.dependsOnOwnProps? proxy.mapToProps(stateOrDispatch, ownProps): proxy.mapToProps(stateOrDispatch)
上两行代码已经将proxy.dependsOnOwnProps和proxy.mapToProps换掉了所以不会形成死循环再次进入当前方法proxy.mapToProp方法。
获取到mapToProps的返回值,检查返回值,如果是function那么这个代理函数就会将这个props当做真正的mapToProps函数。然后再赋值并且执行一遍。
if (typeof props === 'function') {proxy.mapToProps = propsproxy.dependsOnOwnProps = getDependsOnOwnProps(props)props = proxy(stateOrDispatch, ownProps)
}
再接下来则会判断新得到的props是否是简单对象。如果不是简单对象并且不在生产环境中就要报错,否则直接返回props。
小结:wrapMapToPropsFunc
函数将将mapStateToProps和mapDispatchToProps归为一类,都是输入一个值,输出一个对象,过程都一样,所以具体代理的是mapDispatch还是mapState只和入参有关,代理的过程是一样的。
getDependsOnOwnProps
export function getDependsOnOwnProps(mapToProps) {return mapToProps.dependsOnOwnProps !== null &&mapToProps.dependsOnOwnProps !== undefined? Boolean(mapToProps.dependsOnOwnProps): mapToProps.length !== 1
}
这个函数相对简单,如果mapToProps有显示声明是否依赖ownProps如果有将其转换为boolean返回,否则使用隐式判断记mapToProps的入参有几个,如果不是1个(其实这里应该是大于1吧?)则认为入参至少是两个,而第二个参数就希望是ownProps。
verifyPlainObject
这个方法主要验证对象是否是简单对象,具体说只做存储数据用,具体为什么要求mapToProps函数返回的是简单对象,就并没有头绪了。 判断是否是简单对象的方法如下:
export default function isPlainObject(obj) {if (typeof obj !== 'object' || obj === null) return falselet proto = Object.getPrototypeOf(obj)if (proto === null) return truelet baseProto = protowhile (Object.getPrototypeOf(baseProto) !== null) {baseProto = Object.getPrototypeOf(baseProto)}return proto === baseProto
}
如果入参不是对象则不是简单对象,因为连对象都不是。
获取对象原型,如果对象原型是null(Object.create(null))则是简单对象。
遍历原型链,找到原型上倒数第二个节点,与proto对于,如果相等则表示obj是简单对象,否则不是简单对象。
class A extends B {} A的实例就不是简单对象,因为A的原型是B.prototype而不是Object.prototype。
未解决的问题:为什么要遍历而不是检查两层原型链?如:
export default function isPlainObject(obj) {if (typeof obj !== 'object' || obj === null) return falselet proto = Object.getPrototypeOf(obj)if (proto === null) return truereturn Object.getPrototypeOf(proto) === null
}
mapDispatchToPropsFactories
import { bindActionCreators } from 'redux'
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {return typeof mapDispatchToProps === 'function'? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps'): undefined
}export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {return !mapDispatchToProps? wrapMapToPropsConstant(dispatch => ({ dispatch })): undefined
}export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {return mapDispatchToProps && typeof mapDispatchToProps === 'object'? wrapMapToPropsConstant(dispatch =>bindActionCreators(mapDispatchToProps, dispatch)): undefined
}export default [whenMapDispatchToPropsIsFunction,whenMapDispatchToPropsIsMissing,whenMapDispatchToPropsIsObject
]
Factories从右往左mapDispatchToProps分别是object的情况,没有传的情况和是函数的情况。
其中使用的到的wrapMapToPropsConstant
和 wrapMapToPropsFunc
在上面有介绍,而bindActionCreators
的入参希望是一个对象,对象对应的值是actionCreator,执行的结果则是将actionCreator用函数包起来,例如这样:
function bindActionCreator(actionCreator, dispatch) {return function() {return dispatch(actionCreator.apply(this, arguments))}
}
mergePropsFactories
import verifyPlainObject from '../utils/verifyPlainObject'export function defaultMergeProps(stateProps, dispatchProps, ownProps) {return { ...ownProps, ...stateProps, ...dispatchProps }
}export function wrapMergePropsFunc(mergeProps) {return function initMergePropsProxy(dispatch,{ displayName, pure, areMergedPropsEqual }) {let hasRunOnce = falselet mergedPropsreturn function mergePropsProxy(stateProps, dispatchProps, ownProps) {const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps)if (hasRunOnce) {if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))mergedProps = nextMergedProps} else {hasRunOnce = truemergedProps = nextMergedPropsif (process.env.NODE_ENV !== 'production')verifyPlainObject(mergedProps, displayName, 'mergeProps')}return mergedProps}}
}export function whenMergePropsIsFunction(mergeProps) {return typeof mergeProps === 'function'? wrapMergePropsFunc(mergeProps): undefined
}export function whenMergePropsIsOmitted(mergeProps) {return !mergeProps ? () => defaultMergeProps : undefined
}export default [whenMergePropsIsFunction, whenMergePropsIsOmitted]
和上面两个factories一样的模式,主要看下函数wrapMergePropsFunc
主要做的事情是缓存mergedProps,如果对比出
nextMergedProps
计算出来的props和缓存的mergedProps一样,那么就直接返回缓存的mergedProps。其中最终的props主要有三个来源,一个是stateProps,一个是dispatchProps还有一个是ownProps。
- 阅读react-redux源码 - 零
- 阅读react-redux源码 - 一
- 阅读react-redux源码(二) - createConnect、match函数的实现
- 阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories