美文网首页
React Redux源码分析

React Redux源码分析

作者: 贪恋冬天的幸福 | 来源:发表于2020-01-07 22:24 被阅读0次

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

相关文章

网友评论

      本文标题:React Redux源码分析

      本文链接:https://www.haomeiwen.com/subject/wxsdactx.html