美文网首页reactredux
react-redux connect 源码

react-redux connect 源码

作者: xiaohesong | 来源:发表于2018-09-19 16:58 被阅读13次

    今天看了下react-redux的源码,主要来看下connect的方法

    首先找到connect的入口文件。在src/index.js下找到。对应connect文件夹下的connect.js文件。

    • 大致说下源码connect流程
      connect.js对外暴露是通过export default createConnect(), 我们来看createConnect方法
      在这里,他是在对外暴露的时候直接运行,导致对外导出的结果就是他返回的一个connect方法.
      然后再connect里他是返回了一个函数的结果。这个就对应到createHoc所对应的connectAdvanced部分,然后再返回处理过的我们的容器组件.

    • 具体细节
      看下面部分的源码:

    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, {
          // 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
        })
      }
    }
    

    我们知道,在容器组件平时对外暴露的时候是

    const mapStateToProps = (state, ownProps) => ({
    //  ...
    })
    
    const mapDispatchToProps = dispatch => ({
      actions: bindActionCreators(Object.assign({}, actions), dispatch)
    })
    
    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(YourComponent)
    

    我们可以看到基本都是都是传递两个参数,mapStateToProps&mapDispatchToProps, 第三个参数mergeProps并没有传递,这个参数是干什么的?
    他在

    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
    

    这里初始化mergeProps,通过mergePropsFactories来处理里返回的两个函数来处理。
    对应了两个函数对应了mergeProps的两种情况:

    1. whenMergePropsIsFunction存在mergeProps并且是function的时候,会进行处理并返回一个函数,这个函数的第一个参数是一个dispatch,第二个参数是一个对象,并且对象接收三个属性,如果不是一个方法,那就返回undefined.

    2: whenMergePropsIsOmitted方法会判断mergeProps是不是存在,如果存在就返回undefined,否则就会返回一个默认的mergeProps

    mapStateToProps&mapDispatchToPropsmergeProps的初始化类似,都会进行判断再去操作。

    然后connect后面的第四个参数是一个对象,他接收一些属性。

    然后就是下一步了,在使用的时候,我们connect()之后就是继续调用,传参是我们的容器组件。那这时,在源码里就是对应的return connectHOC(selectorFactory, {...}), 这个返回的是一个函数运行的结果,所以我们传参的容器组件这个参数,并不是这里的selectorFactory,而是他运行完成之后的结果所接受的参数。

    上面有提到createHOC对应的是connectAdvanced.然后可以看到createHOC传参的第一个是selectorFactory,就是对应的defaultSelectorFactory,然后第二个参数是一些对象。

    接下来我们就可以看看对应createHOCconnectAdvanced方法了。

    首先看connectAdvanced在参数方面,第一个保持不变,第二个进行了扩展,添加了几个属性。我们还是从对外暴露的接口来看,他直接暴露的是connectAdvanced方法。因为我们在createHOC所需要的是一个结果,所以我们通过源码看看他是怎么运行的.

    在这个方法里,有返回一个方法wrapWithConnect(WrappedComponent){},好家伙,到这里我们可以知道connect里的return createHOC(...)的结果返回的就是这个,然后再看返回的这个函数名称,很见名知意用connect包裹的函数的参数是包裹的组件.那就看看这个函数里面是啥。

    一些基本的处理,然后就是一个connect基于React.Component的继承,最后是返回return hoistStatics(Connect, WrappedComponent),这个hoistStatics是一个类似Object.assigncopyreactstatic property.换句话说,就是把传递的容器组件自定义的静态属性附加到connect组件上去。

    我们再看看这个connect class, 他在构造函数里直接运行了this.initSelector()和this.initSubscription()我们先看下initSelector发现就是使用的selectorFactory方法来处理的,我们就看看这个方法是怎么处理的。对外暴露的方法在这里,他会根据pure属性来确定到底该如何处理。其实我们从这一段注释就可以知道了:

    // If pure is true, the selector returned by selectorFactory will memoize its results,
    // allowing connectAdvanced's shouldComponentUpdate to return false if final
    // props have not changed. If false, the selector will always return a new
    // object and shouldComponentUpdate will always return true.
    

    就是去处理到底是否应该重新渲染,所有如果你想要刷新的情况,可以在connect的传参的第四个对象里改变purefalse.

    然后initSelector同时也会为当前对象创建一个selector

    function makeSelectorStateful(sourceSelector, store) {
      // wrap the selector in an object that tracks its results between runs.
      const selector = {
        run: function runComponentSelector(props) {
          try {
            const nextProps = sourceSelector(store.getState(), props)
            if (nextProps !== selector.props || selector.error) {
              selector.shouldComponentUpdate = true
              selector.props = nextProps
              selector.error = null
            }
          } catch (error) {
            selector.shouldComponentUpdate = true
            selector.error = error
          }
        }
      }
    
      return selector
    }
    

    最后来run,如果符合条件或者出错,便会改变shouldComponentUpdate = true,去重新 render.

    再来看看initSubscription方法:

    initSubscription() {
            if (!shouldHandleStateChanges) return
    
            const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
            this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
    
            this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
          }
    

    先判断shouldHandleStateChanges是不是成立,如果成立则进行,否则返回。成立会进行初始化,正如其名,初始化订阅相关事件。

    这里初始化结束之后,我们看下componentDidMount方法,

    componentDidMount() {
            if (!shouldHandleStateChanges) return
    
            // componentWillMount fires during server side rendering, but componentDidMount and
            // componentWillUnmount do not. Because of this, trySubscribe happens during ...didMount.
            // Otherwise, unsubscription would never take place during SSR, causing a memory leak.
            // To handle the case where a child component may have triggered a state change by
            // dispatching an action in its componentWillMount, we have to re-run the select and maybe
            // re-render.
            this.subscription.trySubscribe()
            this.selector.run(this.props)
            if (this.selector.shouldComponentUpdate) this.forceUpdate()
    }
    

    他也会和initSubscription一样的去判断,确定是否进行下去。下面会进行尝试订阅.然后去运行selector,运行selector之后,会判断shouldComponentUpdate,如果成立,则会进行forceUpdate, 注意: forceUpdate会跳过shouldComponentUpdate的判断.

    最后看下render方法,

    render() {
        const selector = this.selector
        selector.shouldComponentUpdate = false
    
        if (selector.error) {
          throw selector.error
        } else {
          return createElement(WrappedComponent, this.addExtraProps(selector.props))
        }
    }
    

    可以看到.他会对属性进行重写成false,如果报错,就跑出错误,如果正常,那就进行render创建元素到页面。

    第一次看源码,有些东西解释不到位,欢迎提出于原文出处

    相关文章

      网友评论

        本文标题:react-redux connect 源码

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