react-redux源码分析及实现原型(下)

栏目: 编程工具 · 发布时间: 5年前

内容简介:上一次我们讲解了Provider、connect、selectorFactory。这次主要分析 connectAdvanced 这个核心API。在开始之前我们先来看一个工具函数上篇讲过 selector 会将新的值和缓存的值做比较,如果变化,将重新求值并返回,如果没变化,返回缓存的旧值。makeSelectorStateful 函数是对 selector 的封装。正如其名字一样,使selector stateful

上一次我们讲解了Provider、connect、selectorFactory。这次主要分析 connectAdvanced 这个核心API。 react-redux源码分析及实现原型_上

connectAdvanced

在开始之前我们先来看一个 工具 函数

function makeSelectorStateful(sourceSelector, store) {
  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
}
复制代码

上篇讲过 selector 会将新的值和缓存的值做比较,如果变化,将重新求值并返回,如果没变化,返回缓存的旧值。makeSelectorStateful 函数是对 selector 的封装。正如其名字一样,使selector stateful

再介绍一下 hoist-non-react-statics 这个库,作用是避免在使用HOC时,导致类的static方法丢失的问题。详情见react doc

Subscription 是实现react与redux绑定的类,在接下来会用到我们先来看一下

export default class Subscription {
  constructor(store, parentSub, onStateChange) {
    this.store = store
    this.parentSub = parentSub
    this.onStateChange = onStateChange
    this.unsubscribe = null
    this.listeners = nullListeners
  }

  addNestedSub(listener) {
    this.trySubscribe()
    return this.listeners.subscribe(listener)
  }

  notifyNestedSubs() {
    this.listeners.notify()
  }

  isSubscribed() {
    return Boolean(this.unsubscribe)
  }

  trySubscribe() {
    if (!this.unsubscribe) {
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.onStateChange)
        : this.store.subscribe(this.onStateChange)
 
      this.listeners = createListenerCollection()
    }
  }

  tryUnsubscribe() {
    if (this.unsubscribe) {
      this.unsubscribe()
      this.unsubscribe = null
      this.listeners.clear()
      this.listeners = nullListeners
    }
  }
}
复制代码

重点在 trySubscribe 方法,如果 parentSub 存在就将回调函数绑定在父组件上,否则绑定在store.subscribe中 原因是这样可以保证组件的更新顺序,从父到子。

然后可以开始 connectAdvanced

export default function connectAdvanced(
  selectorFactory,
  {
    shouldHandleStateChanges = true,
    storeKey = 'store',
    // 传递给 selectorFactory 的参数
    ...connectOptions
  } = {}
) {
  return function wrapWithConnect(WrappedComponent) {

    //...

    const selectorFactoryOptions = {
      ...connectOptions,
      shouldHandleStateChanges,
      // ...
    }

    class Connect extends Component {
      constructor(props, context) {
        super(props, context)

        this.version = version
        this.state = {}
        this.renderCount = 0
        this.store = props[storeKey] || context[storeKey]
        this.propsMode = Boolean(props[storeKey])
        this.setWrappedInstance = this.setWrappedInstance.bind(this)、

        // selector 与 subscription 的初始化
        this.initSelector()
        this.initSubscription()
      }

      getChildContext() {
        // 如果组件从props里获得store,那么将 context 中的 subscription 传递下去
        // 否则就将传递此组件中的 subscription
        // 子组件使用祖先组件的 subscription 可以保证组件的更新顺序(父 -> 子)。
        // 另外 将store通过props传递下去,这种场景是什么。。。
        const subscription = this.propsMode ? null : this.subscription
        return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
      }

      componentDidMount() {
        // shouldHandleStateChanges === Boolean(mapStateToProps)
        // 如果没有 mapStateToProps 组件不需要监听store变化
        if (!shouldHandleStateChanges) return

        // 由于 componentWillMount 会在ssr中触发,而 componentDidMount、componentWillUnmount不会。
        // 如果将subscription放在 componentWillMount中,那么 unsubscription 将不会被触发,将会导致内存泄漏。
        this.subscription.trySubscribe()

        // 为了防止子组件在 componentWillMount 中调用dipatch 所以这里需要在重新计算一次
        // 因为子组件的 componentWillMount 先于组件的 componentDidMount 发生,此时还没有执行 trySubscribe
        this.selector.run(this.props)
        if (this.selector.shouldComponentUpdate) this.forceUpdate()
      }

      componentWillReceiveProps(nextProps) {
        this.selector.run(nextProps)
      }

      shouldComponentUpdate() {
        return this.selector.shouldComponentUpdate
      }

      componentWillUnmount() {
        if (this.subscription) this.subscription.tryUnsubscribe()
        this.subscription = null
        this.notifyNestedSubs = noop
        this.store = null
        this.selector.run = noop
        this.selector.shouldComponentUpdate = false
      }

      initSelector() {
        const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
        this.selector = makeSelectorStateful(sourceSelector, this.store)
        this.selector.run(this.props)
      }

      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.subscription就为null了。这里将notifyNestedSubs拷贝一次。
        // 并且在componentWillUnmount 中 this.notifyNestedSubs = noop,
        this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
      }

      onStateChange() {
        this.selector.run(this.props)

        if (!this.selector.shouldComponentUpdate) {
          // 如果不需要更新则通知子组件
          this.notifyNestedSubs()
        } else {
          // 如果需要更新则在更新之后,再通知子组件
          this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate

          // 组件更新
          this.setState(dummyState)
        }
      }

      notifyNestedSubsOnComponentDidUpdate() {
        // 避免重复通知
        this.componentDidUpdate = undefined
        this.notifyNestedSubs()
      }

      isSubscribed() {
        return Boolean(this.subscription) && this.subscription.isSubscribed()
      }

      render() {
        const selector = this.selector
        selector.shouldComponentUpdate = false

        if (selector.error) {
          throw selector.error
        } else {
          return createElement(WrappedComponent, this.addExtraProps(selector.props))
        }
      }
    }

    /* eslint-enable react/no-deprecated */

    Connect.WrappedComponent = WrappedComponent
    Connect.displayName = displayName
    Connect.childContextTypes = childContextTypes
    Connect.contextTypes = contextTypes
    Connect.propTypes = contextTypes

    return hoistStatics(Connect, WrappedComponent)
  }
}
复制代码

over~ 是不是觉得react-redux很简单? 接下来我会将react技术栈中常用的库(react, react-router, redux, redux-saga, dva)源码分析一遍,喜欢的话。可以watch me!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

30天自制操作系统

30天自制操作系统

[日] 川合秀实 / 周自恒、李黎明、曾祥江、张文旭 / 人民邮电出版社 / 2012-8 / 99.00元

自己编写一个操作系统,是许多程序员的梦想。也许有人曾经挑战过,但因为太难而放弃了。其实你错了,你的失败并不是因为编写操作系统太难,而是因为没有人告诉你那其实是一件很简单的事。那么,你想不想再挑战一次呢? 这是一本兼具趣味性、实用性与学习性的书籍。作者从计算机的构造、汇编语言、C语言开始解说,让你在实践中掌握算法。在这本书的指导下,从零编写所有代码,30天后就可以制作出一个具有窗口系统的32位......一起来看看 《30天自制操作系统》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具