1. 程式人生 > >react-redux connect原始碼解讀

react-redux connect原始碼解讀

今天看了下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建立元素到頁面。

第一次看原始碼,有些東西解釋不到位,歡迎提出於原文出處