connect.js文件的代码可简单表示如下:
export default (function createConnect() {
//...
})();
使用了立即执行函数,这样做的好处是为接下来的操作提前添加一些配置参数,例如:
{
//...
//defaultMapStateToPropsFactories 来自于 mapStateToProps 文件
//defaultMapDispatchToPropsFactories 来自于 mapDispatchToProps 文件
//defaultMergePropsFactories 来自于 mergeProps 文件
//defaultSelectorFactory来自于 selectorFactory 文件
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
}
这个立即执行函数执行后,配置完成,返回的 connect 函数是真正要使用的函数。
return function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true,
areStatesEqual = strictEqual,
areOwnPropsEqual = shallowEqual,
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
...extraOptions
} = {}
) {
//...
}
从 connect 函数参数上我们可以看出, 我们需要依次传入 mapStateToProps 、mapDispatchToProps 、mergeProps ,其余参与可以使用默认配置:
- pure = true,表示是否使用PureComponent,true则使用,false则使用 Component;
- areStatesEqual 是严格相等比较;
- areOwnPropsEqual、areStatePropsEqual、areMergedPropsEqual 是浅比较,提供在工具文件夹中 utils/shallowEqual 文件中的函数;
对于 mapStateToProps 、mapDispatchToProps 、mergeProps,如果没有传入,该如何处理?如果传入了,那应该传入什么类型呢?关于这些问题,我们具体看一下接下来的代码。
//...
const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps'
)
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps'
)
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
//...
分别将 mapStateToProps、mapDispatchToProps 、mergeProps 作为参数传入,使用 match 函数做了处理,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 函数倒序遍历传入的 mapDispatchToPropsFactories 数组参数,依次取出其中的函数对 arg 参数做处理,直到有结果返回,即退出函数。如果最后无结果,则返回接受 dispatch, options 参数的函数,此函数抛出错误。
那我们分别看下 mapStateToPropsFactories,mapDispatchToPropsFactories,mergePropsFactories 中存放了哪些函数。之前得知,这三个参数都是默认配置,即 defaultMapStateToPropsFactories,defaultMapDispatchToPropsFactories,defaultMergePropsFactories。
mapStateToProps
文件中代码如下:
//...
export function whenMapStateToPropsIsFunction(mapStateToProps) {
return typeof mapStateToProps === 'function'
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
: undefined
}
export function whenMapStateToPropsIsMissing(mapStateToProps) {
return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}
export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
由此可见,mapStateToProps 没有传入时,返回默认 wrapMapToPropsConstant(() => ({})),如果传入了,如果是函数,返回 wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')。如果不是函数,则由 match 函数中返回默认提示函数。
mapDispatchToProps
文件中代码如下:
//...
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
]
由此可见,mapDispatchToProps 如果没有传入,返回默认 wrapMapToPropsConstant(dispatch => ({ dispatch })),如果传入了,如果是对象,返回 wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch)),如果是函数,返回 wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps'),如果都不是,则由 match 函数中返回默认提示函数。
mergeProps
文件中代码如下:
//...
export function whenMergePropsIsFunction(mergeProps) {
return typeof mergeProps === 'function'
? wrapMergePropsFunc(mergeProps)
: undefined
}
export function whenMergePropsIsOmitted(mergeProps) {
return !mergeProps ? () => defaultMergeProps : undefined
}
export default [whenMergePropsIsFunction, whenMergePropsIsOmitted]
由此可见,mergeProps 没有传入时,返回默认 () => defaultMergeProps,如果传入了,如果是函数,返回 wrapMergePropsFunc(mergeProps),如果不是函数,则由 match 函数中返回默认提示函数。
关于 wrapMapToPropsConstant、wrapMapToPropsFunc 与 wrapMergePropsFunc ,我们发现返回的都是接受 dispatch 为第一个参数的函数。关于具体的操作,后续我们再深入了解。
//....
return connectHOC(selectorFactory, {
// used in error messages
methodName: 'connect',
// used to compute Connect's displayName from the wrapped component's displayName.
getDisplayName: name => `Connect(${name})`,
// if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
shouldHandleStateChanges: Boolean(mapStateToProps),
// passed through to selectorFactory
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
// any extra options args can override defaults of connect or connectAdvanced
...extraOptions
})
返回一个执行过的 connectHOC 函数,此函数接受 selectorFactory 参数 和 额外的配置参数,包括
- methodName:在报错信息中展示;
- getDisplayName:在 wrappedComponent 的展示名字前加 Connect 作为 Connect 组件的展示名字;
- shouldHandleStateChanges:如果 mapStateToProps 参数没有传入,则 Connect 组件不需要监听store的状态变化;
- 依次传入新生成的三个 factory 函数和之前的配置参数。
为什么这里也用了一个执行过的函数,好处同上,便于将配置参数传入。
接下来看一下 connectHOC 函数,从 creactConnect 立即执行函数可知,connectHOC 是默认的 connectAdvanced。
在connectAdvanced.js
文件中:
export default function connectAdvanced(...) {
//...
return function wrapWithConnect(WrappedComponent) {
//...
function makeChildElementSelector() {
//...
return function selectChildElement(
WrappedComponent,
childProps,
forwardRef
) {
if (...) {
//...
lastChildElement = (
<WrappedComponent {...childProps} ref={forwardRef} />
)
}
return lastChildElement
}
}
class Connect extends OuterBaseComponent {
constructor(props) {
super(props)
//...
this.selectChildElement = makeChildElementSelector()
this.indirectRenderWrappedComponent = this.indirectRenderWrappedComponent.bind(this)
}
indirectRenderWrappedComponent(value) {
// calling renderWrappedComponent on prototype from indirectRenderWrappedComponent bound to `this`
return this.renderWrappedComponent(value)
}
renderWrappedComponent(value) {
//...
return this.selectChildElement(
WrappedComponent,
derivedProps,
forwardedRef
)
}
render() {
//...
return (
<ContextToUse.Consumer>
{this.indirectRenderWrappedComponent}
</ContextToUse.Consumer>
)
}
}
//...
return hoistStatics(Connect, WrappedComponent)
}
}
这里 wrapWithConnect 是高阶组件,传入 WrappedComponent 组件,返回 Connect 组件。
connectAdvanced 函数传入了 selectorFactory 函数,关于它的具体含义,我们看一下注释(翻译):
selectorFactory 函数为从state、props和dispatch中生成新的 props 的 selector 函数负责,示例如下:
export default connectAdvanced((dispatch, options) => (state, props) => ({
thing: state.things[props.thingId],
saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)),
}))(YourComponent)
为 factory 提供 dispatch 参数,因此 selectorFactories 可以绑定 actionCreators 在他们的 selector 的外面作为优化。传递给 connectAdvanced 的选项被传递给 selectorFactory 以及 displayName 和 WrappedComponent 作为第二个参数。请注意,selectorFactory 负责所有内传和外传的 props 的缓存/记忆。不要直接使 connectAdvanced,在调用您的选择器,否则 Connect 组件在每个 state 或 props 变化的时候将重新渲染。
关于 selectorFactory 函数的具体操作,随后将进行分析。
我们先着重分析高阶组件的内部:
//... pure 为 true,则继承 PureComponent,为 false,则继承 Component。
const { pure } = connectOptions
let OuterBaseComponent = Component
if (pure) {
OuterBaseComponent = PureComponent
}
//...
function makeDerivedPropsSelector() {
let lastProps, lastState, lastDerivedProps, lastStore, lastSelectorFactoryOptions, sourceSelector
return function selectDerivedProps(...) {
//...
}
}
function makeChildElementSelector() {
let lastChildProps, lastForwardRef, lastChildElement, lastComponent
return function selectChildElement(...) {
//...
}
}
class Connect extends OuterBaseComponent {
constructor(props) {
//...
this.selectDerivedProps = makeDerivedPropsSelector()
this.selectChildElement = makeChildElementSelector()
//...
}
}
makeDerivedPropsSelector 与 makeChildElementSelector 函数采用闭包的形式,保存了被记忆的内部变量,这种方法很实用,如果返回的函数作为构造函数,则在闭包函数返回之前声明的变量就相当于私有静态成员,具有同一个构造函数创建的所有对象共享该成员,构造函数外部不可访问该成员的特点。在这里这些变量起到记忆的功能,可记忆上次的传参与结果。
//...
render() {
//判断是否有自定义的上下文传入,如果没有,则使用在创建 Provider 时候的 Context,
//因此 Context.Consumer 可以监听到 Provider 的 value 值的变化。
const ContextToUse = this.props.context && this.props.context.Consumer &&
isContextConsumer(<this.props.context.Consumer />) ? this.props.context : Context;
return (
<ContextToUse.Consumer>
{this.indirectRenderWrappedComponent}
</ContextToUse.Consumer>
)
}
this.indirectRenderWrappedComponent 是一个接受 value 值的函数。代码如下:
//...
constructor(props) {
//... 绑定 this 为当前实例
this.indirectRenderWrappedComponent = this.indirectRenderWrappedComponent.bind(this)
}
indirectRenderWrappedComponent(value) {
// calling renderWrappedComponent on prototype from indirectRenderWrappedComponent bound to `this`
return this.renderWrappedComponent(value)
}
renderWrappedComponent(value) {
const { storeState, store } = value
//...
}
在 renderWrappedComponent 函数中,入参 value 等同于 Provider 的 value,因此在这里可以取出 storeState,和 store。
//...
let derivedProps = this.selectDerivedProps(storeState, wrapperProps, store, selectorFactoryOptions)
return this.selectChildElement(WrappedComponent, derivedProps, forwardedRef)
这里调用 selectDerivedProps 和 selectChildElement 函数,我们看下函数内部:
//selectDerivedProps 函数内部
if (pure && lastProps === props && lastState === state) {
return lastDerivedProps; //如果是 PureComponent,props 与 state 不变的情况下,返回上次结果值
}
//如果是 PureComponent,但 props 与 state 任一发生变化
//或者不是 PureComponent,则无论 props 与 state 是否发生变化,都更新 props、state 和 derivedProps
//只要 store 与 selectorFactoryOptions 发生变化,则更新 sourceSelector
if (store !== lastStore || lastSelectorFactoryOptions !== selectorFactoryOptions) {
lastStore = store
lastSelectorFactoryOptions = selectorFactoryOptions
sourceSelector = selectorFactory(store.dispatch, selectorFactoryOptions)
}
lastProps = props; //记忆入参 props
lastState = state; //记忆入参 state
const nextProps = sourceSelector(state, props);
lastDerivedProps = nextProps; //记忆结果 derivedProps
return lastDerivedProps;
//selectChildElement 函数内部
//如果 childProps、forwardRef、WrappedComponent 发生变更,则更新 WrappedComponent
//如果都没有发生变更,返回上次结果值
if (childProps !== lastChildProps || forwardRef !== lastForwardRef || lastComponent !== WrappedComponent) {
lastChildProps = childProps;
lastForwardRef = forwardRef;
lastComponent = WrappedComponent;
lastChildElement = <WrappedComponent {...childProps} ref={forwardRef} />;
}
return lastChildElement;
WrappedComponent 组件实例是由一开始传入的,一般不变化,forwardRef 同理,这里着重关心 childProps 是否发生变化。
childProps 是由函数 selectDerivedProps 返回的,是否发生变化由 selectDerivedProps 函数决定,如果 sourceSelector 返回的都是全新的对象,则 Component 函数无论 props 与 state 是否变化,都会生成新的对象,则每次都会更新 lastChildElement。
sourceSelector 函数是由 selectorFactory 函数返回的,如果 store 与 selectorFactoryOptions 不更新,则保持原函数调用,否则由 selectorFactory 函数返回新函数再调用。
selectorFactory 函数接受了 store.dispatch 与 selectorFactoryOptions 参数,selectorFactoryOptions 中包含connectOptions,
const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
displayName,
wrappedComponentName,
WrappedComponent
}
connectOptions 参数是由 connectHOC 传入的,包含
{
// used in error messages
methodName: 'connect',
// used to compute Connect's displayName from the wrapped component's displayName.
getDisplayName: name => `Connect(${name})`
// if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
shouldHandleStateChanges: Boolean(mapStateToProps),
// passed through to selectorFactory
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
// any extra options args can override defaults of connect or connectAdvanced
...extraOptions
}
selectorFactory 函数调用内部如下:
export default function finalPropsSelectorFactory(dispatch,
{initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options}
) {
//...
}
事实上调用 initMapStateToProps、initMapDispatchToProps、initMergeProps,传入 dispatch 与 剩余参数 options,
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
这里对 pure 值的真假情况做了区分,这里的注释对其进行了解释(翻译):
如果pure为真,则selectorFactory返回的选择器将默记其结果,允许在最后的 props 没有发生改变时 connectAdvanced 的 shouldComponentUpdate 返回 false 。如果为 false ,选择器将始终返回一个新对象,而shouldComponentUpdate 将始终返回 true 。
const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory;
return selectorFactory(...);
如果我们没有在 connect 函数中传入 pure: false,那默认 pure = true,事实上我们一般不会去覆盖掉默认参数 pure 的配置。因此,我们这里只看 pureFinalPropsSelectorFactory 的实现。
//关键参数:areStatesEqual 默认是严格相等比较、areOwnPropsEqual、areStatePropsEqual 默认是浅比较
export function pureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
{ areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
//...
}
返回值是一个接受 state、props 参数的函数,这样就组成了 (dispatch, options) => (state, props) => {} 的高阶函数。
//...
return function pureFinalPropsSelector(nextState, nextOwnProps) {
return hasRunAtLeastOnce
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
再传入 state 与 props:
// nextProps 是 handleSubsequentCalls 或者 handleFirstCall 函数的返回值
const nextProps = sourceSelector(state, props)
如果是第一次执行,调用 handleFirstCall 函数:
//首次执行不进行任何比较,直接获取 stateProps、dispatchProps、mergedProps
//对 ownProps, stateProps, dispatchProps 执行操作获取最终 props
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
}
如果非首次执行,调用 handleSubsequentCalls 函数:
function handleSubsequentCalls(nextState, nextOwnProps) {
//对 nextOwnProps 与 记忆的 ownProps 进行比较
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
//对 nextState 与 记忆的 state 进行比较
const stateChanged = !areStatesEqual(nextState, state)
//记忆最新值
state = nextState
ownProps = nextOwnProps
//如果都更新了,生成新的 props 和 state
if (propsChanged && stateChanged) return handleNewPropsAndNewState()
//如果ownProps更新了,生成新的 props
if (propsChanged) return handleNewProps()
//如果state更新了,生成新的 state
if (stateChanged) return handleNewState()
//如果都没有更新,返回记忆的 mergedProps 值
return mergedProps
}
handleNewPropsAndNewState、handleNewProps、handleNewState 都会更新 mergedProps 值,如下所示:
function handleNewPropsAndNewState() {
//...
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
}
function handleNewProps() {
//...
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
}
function handleNewState() {
//...
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
}
三个函数中会调用 mapStateToProps 与 mapDispatchToProps 更新 stateProps, dispatchProps,最终生成最后的 mergedProps。
impureFinalPropsSelectorFactory 与 pureFinalPropsSelectorFactory 的区别之处就在与,pureFinalPropsSelectorFactory 可能返回旧的对象,而 impureFinalPropsSelectorFactory 总是返回一个新对象。
而返回对象的区别,会导致是否更新 WappedComponent 的区别:
if (...|| childProps !== lastChildProps ||...) {
lastChildProps = childProps
//....
lastChildElement = <WrappedComponent {...childProps} ref={forwardRef} />
}
到此为止,React Redux 的主要流程分析结束,高阶组件的封装,是为了让子组件脱离对context的依赖,使其只需要是个纯组件即可,就可以增强子组件的可复用能力。基于 Context 的 Provider 提供 store,高阶组件 Connect 使用 Context.Consumer 接受 Context.Provider 的 value 值,因此达到不需要在组件之间深度传递 store 的目的。对于 Context 的使用还有很多,React Redux 更新到7.1版本,增加了 Hooks 版本,因此我们可以使用 Hooks 替代高阶组件,来更方便的使用 Redux。有兴趣可以关注最新 React Redux 版本,仓库地址:https://github.com/reduxjs/react-redux
。
网友评论