react-redux源碼分析及實現原型(下)

上一次咱們講解了Provider、connect、selectorFactory。此次主要分析 connectAdvanced 這個核心API。 react-redux源碼分析及實現原型_上html

connectAdvanced

在開始以前咱們先來看一個工具函數react

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 statefulredux

再介紹一下hoist-non-react-statics這個庫,做用是避免在使用HOC時,致使類的static方法丟失的問題。詳情見react doc緩存

Subscription是實現react與redux綁定的類,在接下來會用到咱們先來看一下react-router

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中 緣由是這樣能夠保證組件的更新順序,從父到子。app

而後能夠開始 connectAdvancedide

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!函數

相關文章
相關標籤/搜索