美文网首页
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