閱讀react-redux源碼(二) - createConnect、match函數的實現

上一節看了Provider組件的實現,主要做的事情就是通過Context透傳了來自redux的store和監聽store變化的事件對象Subscription的實例。

本節會深入到connect組件的內部查看實現方式,整個connect組件的實現相對複雜,並且巧妙。

通過入口文件可以知道connect組件是/src/connect/connect.js的默認導出。文件內容如下:

import connectAdvanced from '../components/connectAdvanced'
import shallowEqual from '../utils/shallowEqual'
import defaultMapDispatchToPropsFactories from './mapDispatchToProps'
import defaultMapStateToPropsFactories from './mapStateToProps'
import defaultMergePropsFactories from './mergeProps'
import defaultSelectorFactory from './selectorFactory'

/*
  connect is a facade over connectAdvanced. It turns its args into a compatible
  selectorFactory, which has the signature:

    (dispatch, options) => (nextState, nextOwnProps) => nextFinalProps
  
  connect passes its args to connectAdvanced as options, which will in turn pass them to
  selectorFactory each time a Connect component instance is instantiated or hot reloaded.

  selectorFactory returns a final props selector from its mapStateToProps,
  mapStateToPropsFactories, mapDispatchToProps, mapDispatchToPropsFactories, mergeProps,
  mergePropsFactories, and pure args.

  The resulting final props selector is called by the Connect component instance whenever
  it receives new props or store state.
 */
function match(arg, factories, name) {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg)
    if (result) return result
  }

  return (dispatch, options) => {
    throw new Error(
      `Invalid value of type ${typeof arg} for ${name} argument when connecting component ${
        options.wrappedComponentName
      }.`
    )
  }
}

function strictEqual(a, b) {
  return a === b
}

// createConnect with default args builds the 'official' connect behavior. Calling it with
// different options opens up some testing and extensibility scenarios
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
    })
  }
}

export default /*#__PURE__*/ createConnect()

createConnect

首先看看createConnect,關於函數如果有註釋先看註釋然後主要看函數名,一個好的函數名會表明這個函數是做什麼的。再看函數的入參和函數的出參,最後可以看函數的實現方式。

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
    } = {}
  ) {
  }
}

首先這個函數定義參數的形式有點奇怪,function name({a = 1, b = 2} = {})這種形式。這其實是JS中的命名參數的寫法,和Python、Dart之類的語言中提供的命名參數特性類似。入參的賦值和名字有關,而不是位置。

而入參分別是connectHOC還有剩下來的幾個factory,分別是生產mapStateToProps的工廠、生產mapDispathToProps的工廠,生產mergeProps的工廠和生產選擇器的工廠。

後面createConnect函數會返回connect函數,而這個connect函數就是連接組件用到的組件。

在底部會執行createConnect方法則會得到該方法返回的connect函數,在組件中通過connect函數可以連接到頂層Provider提供的store和Subscribtion實例。

connect函數

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
    })
  }

connect函數就是我們用於連接store的函數。connect函數的入參分別是 mapStateToPropsmapDispatchToPropsmergeProps和一個配置對象。

首先看下入參的配置對象:

{
    pure = true,
    areStatesEqual = strictEqual,
    areOwnPropsEqual = shallowEqual,
    areStatePropsEqual = shallowEqual,
    areMergedPropsEqual = shallowEqual,
    ...extraOptions
} = {}

配置對象定義了用於對比數據的函數,分別是對比store中的state,對比被包裹組件自身的props,對比被組件選中的store中的state,對比被包裹組件自身的props和來自store的state被選中的state。

這裏要注意,我們知道一個連接到store的組件的props會有兩個來源,一個是store,包括store中的state和dispatch。還有一個是父組件傳遞給組件的props。 而要做到性能最優則是接收到新的值時對這些值分別對比,如果沒有改變則不需要更新組件,以優化組件性能。

const initMapStateToProps = match(
  mapStateToProps,
  mapStateToPropsFactories,
  'mapStateToProps'
)
const initMapDispatchToProps = match(
  mapDispatchToProps,
  mapDispatchToPropsFactories,
  'mapDispatchToProps'
)
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

這段代碼統一處理了三個工廠函數生成目標函數的過程。

function match(arg, factories, name) {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg)
    if (result) return result
  }

  return (dispatch, options) => {
    throw new Error(
      `Invalid value of type ${typeof arg} for ${name} argument when connecting component ${
        options.wrappedComponentName
      }.`
    )
  }
}

factories是數組,遍歷這個數組,找到一個真的返回值,返回出去,否則返回一個會拋出錯誤的方法。這是責任鏈模式,factories是一個鏈,數組的每一項只處理自身的情況,如果匹配則返回,否則返回undefined通知match繼續遍歷鏈。

factories對應的數據結構式[() => (fn || undefined), () => (fn || undefined), ...]

注意:match從右往左遍歷factories數組。

整理好相關入參,全部傳遞給connectHOC,這個高階組件是整個connect的核心,如何訂閱store更新,如何防止store的更新引起不必要的渲染,如何處理ref的獲取,如何使用通過props傳入的context和store,如何透傳父組件傳入的props。這些都會在connect中處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章