美文网首页
react-redux源码分析

react-redux源码分析

作者: yongningfu | 来源:发表于2017-04-01 21:44 被阅读0次

    react-redux源码分析

    起源: 之前在做react的项目的时候,由于临时要发一个异步请求,而不把这个请求放在redux管理之下,于是在componetDidMount里面发起请求,然后进行setState

    componentDidMount() {
      setTimeout(() => {
        setState({xx:yy});
      }, 2000)
    }
    

    请问上面的代码有问题吗?
    上面的代码是有问题的, 原因在于如果组件立即销毁(unmount)的话,而当前请求还没发完,岂不是造成了在一个 unmount组件里面setState了么? 是的,确实如此,当前我就在想了,为何在redux(dva)管理下,我们进行 dispatch一个action, 这里有可能进行异步操作, 操作完成后,redux更新执行reducers,更新state,再更新组件,为何不会报错?因为异步的时候,我有可能直接销毁了这个组件,但是后面redux有调用了它的setState,岂不是应该报错?,于是我对此进行了探考。

    当然 这里就不分析redux代码了,没看过的先去看一遍redux的代码
    先看看react-redux的源代码结构

    │  index.js
    │
    ├─components
    │      connectAdvanced.js   //最关键的
    │      Provider.js     //作用为传递上下文中的store
    │
    ├─connect
    │      connect.js  //其实只是对connectAdvanced套一层而已
    │      mapDispatchToProps.js
    │      mapStateToProps.js
    │      mergeProps.js  //将多个对象进行合并
    │      selectorFactory.js  //这个也非常关键 selector作用为得到connnect组件属性
    │      verifySubselectors.js
    │      wrapMapToProps.js
    │
    └─utils
            PropTypes.js
            shallowEqual.js
            Subscription.js //非常重要
            verifyPlainObject.js
            warning.js
            wrapActionCreators.js
    

    上面我标记的那几个文件是对于理解来说最关键的文件,其他的文件自己看看即可

    我们知道,一个Provider下面 管理者许多 Connect组件, 其实一个Connect组件下面,还可以有其他Connect组件,react-redux有处理了这一点

    源码里有个非常重要的概念是 selector,这个selector的作用是对 Connect组件的props进行merge,然后判断前后是否一致,是的话,Connect组件就不更新,不是的话,组件就更新

    在selectorFactory.js里面,有两个创建selector的工厂,pureFinalPropsSelectorFactoryimpureFinalPropsSelectorFactory, 这个
    impureFinalPropsSelectorFactory做法比较暴力,每次都会生成一个新的属性对象

     // selectr的创建函数
      return function impureFinalPropsSelector(state, ownProps) {
        return mergeProps(
          mapStateToProps(state, ownProps),
          mapDispatchToProps(dispatch, ownProps),
          ownProps
        )
      }
    

    mergeProps 只是把上面的三个对象和并成一个对象,然后得到Connnect组件的属性值,然后进行注入。 而 pureFinalPropsSelectorFactory呢? 它会缓存上一次生成的 属性值,然后每当要创建新的selector的时候,它会新进行判断 state是否更新,ownProps是否进行更新,如果都不进行更新的话,直接返回上一次缓存的值,这里注意一下,其实在Connect组件里面已经帮我们做了一层优化,

            if (nextProps !== selector.props || selector.error) {
              //设置更新的标志
              selector.shouldComponentUpdate = true
              selector.props = nextProps
              selector.error = null
            }
    

    如果前后的对象不是同一个对象的话, 那么selector.shouldComponentUpdate = true,即更新,如果是同一个的话,就不更新了,它是根据Connect组件的 merge后的属性得的结论来的。但是默认的情况下,使用的是impureFinalPropsSelectorFactory

      const selectorFactory = options.pure
        ? pureFinalPropsSelectorFactory
        : impureFinalPropsSelectorFactory //默认使用
    

    如果你想要那个缓存结果的话,不妨考虑一下,如何在这里进行优化。
    所以使用默认的 impureFinalPropsSelectorFactory , 这个nextProps !== selector.props判断就无效了。

    在selector有个run, 作用就是计算出 Connect前后的props,然后进行比较判断是否进行更新。

    下面就是最重点的了
    react-redux如何和redux联系在一起,即redux在dispatch的时候,是如何通知react进行更新的?
    我们知道,redux实际也是一个事件的订阅机制,它和react联系主要在Connect组件,它会订阅Connect组件里面的onStateChange函数,当redux进行dispatch的时候,就会触发Connect组件的onStateChange函数,那么就会触发组件的setState从而进行更新
    我们看看 Connect组件的 onStateChange定义

          onStateChange() {
            this.selector.run(this.props) 
            if (!this.selector.shouldComponentUpdate) {
              this.notifyNestedSubs()
            } else {
              this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
              this.setState(dummyState)
            }
          }
    

    这个this.selector.run(this.props) 是干啥的?前面有提到过, 重新计算当前Connect组件的props,然后和前一次进行比较,如果不是同一个对象的话,就设置
    this.selector.shouldComponentUpdate = true, 即更新当前组件
    如果是同一个对象话,自然 this.selector.shouldComponentUpdate = false;
    接着往下看
    如果当前Connect不更改下,立即执行this.notifyNestedSubs()
    这个是啥意思?是这样的,每个Connect组件里面都有一个subscription对象,它也是一个订阅模型,每个父的Connect订阅的是 子Connect组件的onStateChange函数,而父的Connect的onStateChange函数,被谁订阅呢?当然是store(redux)啦, 即流程为
    dispathc(action)---触发store的订阅即父的onStateChange---父的onStateChange触发即触发子Connect的onStateChange,这样就能层层更新了。

    我们看看 他们是在哪了完成订阅的 每个Connect组件里面有个

          initSubscription() {
            if (!shouldHandleStateChanges) return
            //父Sub从哪里过来
            const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
    
            //在Connect组件里面新建一个Subscription
            this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
    
            this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
          }
    

    initSubscription里面完成订阅,会在 construct里面调用。 什么时候会挂载带会把onStateChange挂载到store订阅里面,直接看 Subscription.js里面的trySubscriptiion定义

      trySubscribe() {
        if (!this.unsubscribe) {
          this.unsubscribe = this.parentSub
            ? this.parentSub.addNestedSub(this.onStateChange)
            : this.store.subscribe(this.onStateChange) //没有父sub,就把组件的 
           //  onStateChange加入到 redux的store中
          this.listeners = createListenerCollection()
        }
      }
    

    最后, 当组件unmount后, 如何让dispathc的时候,不更新(即不用setState)?

          componentWillUnmount() {
    
            //容器组件卸载后,取消当前的订阅
            if (this.subscription) this.subscription.tryUnsubscribe() //取消下面子Connect的更新
            this.subscription = null
            this.notifyNestedSubs = noop
            this.store = null
            this.selector.run = noop
            //设置为不可以更新---!!!这个也是为什么,redux管理下的Connect容器组件 调用异步
            //异步里面有setState,然后理解退出,这个时候异步还没完成,但是组件已经unmount了,
            //而组件并没有发生 在unmount组件上面使用setState错误的原因 !!!
            this.selector.shouldComponentUpdate = false
          }
    

    相关文章

      网友评论

          本文标题:react-redux源码分析

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