React Redux: 從文檔看源碼 - Connect工具類篇(1)

注:這篇文章只是講解React Redux這一層,並不包含Redux部分。Redux有計劃去學習,等之後學習了Redux源碼之後再作分析;
注:代碼基於如今 (2016.12.29) React Redux 的最新版本 (5.0.1)。javascript

Connect工具類篇

這裏講的實際上是connect方法的基礎,原本是準備先把connectAdvanced和Provider寫完,再專門寫connect和相關方法的,可是發現connectAdvanced也用到了不少這些基礎方法,因此就停下來,寫下面的這些東西。html

以前沒有上過代碼結構圖,這裏貼張圖上來(5.0.1版本)
clipboard.pngjava

React Redux在最近的幾個版本中對代碼作了拆分和優化,以前看4.x的代碼的時候,connect下面全部的方法都在一個文件裏面,並且邏輯也不夠清晰。當時原本想吐槽他們的源代碼的,可是升級到5.x之後,就發現代碼清晰不少,只不過代碼邏輯複雜度更上一層樓。。。react

dependsOnOwnProps

單獨提出來這個屬性,作一個簡單的說明。dependsOnOwnProps屬性並非對外的屬性,而是代碼內部邏輯使用的,會在多個方法中用到,這個屬性主要是針對connect的兩個參數mapStateToProps, mapDispatchToProps。git

根據文檔,mapStateToProps能夠是function或者null, mapDispatchToProps能夠是function, object或者null。若是是function,當定義的時候,能夠選擇是否傳入ownProps對象,好比function mapStateToProps(state, ownProps) {},這就說明這個function的返回結果多是基於ownProps的,因此每次ownProps發生改變的時候,都須要調用這個方法進行更新。github

因此dependsOnOwnProps就是當ownProps更新的時候,用來判斷是否須要從新調用對應方法獲取新的結果。redux

wrapMapToProps.js

getDependsOnownProps

這段代碼主要的做用就是判斷map(State/Dispptch)ToProps方法是否須要ownProps。
返回值決定了在props更新的時候,是否要調用map(State/Dispptch)ToProps方法進行更新segmentfault

export function getDependsOnOwnProps(mapToProps) {
  return (mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined)
    ? Boolean(mapToProps.dependsOnOwnProps)
    : mapToProps.length !== 1
}

這裏經過判斷方法的length來進行判斷是否須要ownProps,當mapToProps.length !== 1的時候,就是須要。反之,就是不須要。 數組

這裏有幾種狀況:app

  1. function mapStateToProps(state){},那麼mapStateToProps.length === 1,不須要ownProps

  2. function mapStateToProps(state, ownProps){}, 那麼mapStateToProps.length === 2, 須要ownProps

  3. function mapStateToProps(){ arguments[0]; } 或者 function mapStateToProps(...args){}, 那麼mapStateToProps.length === 0, 因爲沒法經過定義判斷是否須要ownProps,因此默認是須要

  4. 若是以前已經設置過了,那麼就不須要設置了,重用Boolean(mapToProps.dependsOnOwnProps)

wrapMapToPropsConstant

首先,根據文檔mapDispatchToProps是optional的,並且能夠是function或者object。
這塊代碼主要針對mapDispatchToProps是object或者null的狀況,把這個對象進行包裝,生成(dispatch, options)=>()=>dispatchedActions的方法,並添加dependsOnOwnProps屬性。

export function wrapMapToPropsConstant(getConstant) {
  return function initConstantSelector(dispatch, options) {
    const constant = getConstant(dispatch, options)

    function constantSelector() { return constant }
    constantSelector.dependsOnOwnProps = false 
    return constantSelector
  }
}

首先查看一下這個方法的調用,在mapDispatchToProps.js裏面有兩處調用:

export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {...}

export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
  return (!mapDispatchToProps)
    ? wrapMapToPropsConstant(dispatch => ({ dispatch })) // 會生成(dispatch, options)=>()=>({dispatch})
    : undefined
}

export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
  return (mapDispatchToProps && typeof mapDispatchToProps === 'object')
    ? wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch)) // 會生成(dispatch, options)=>()=>bindActionCreators(mapDispatchToProps, dispatch),也就是綁定過dispatch的actions
    : undefined
}

在這裏,若是mapDispatchToProps爲null或者是object的時候,調用wrapMapToPropsConstant方法。
而在Object裏面,因爲調用了bindActionCreators方法,因此最後生成的都是綁定過dispatch的actions,這也就是爲何在connect的組件中,直接調用this.props.action()就能夠通知redux,而不是dispatch(this.props.action()).

文檔中也提到:

若是傳入的時object,那麼裏面全部的function都會被認爲是一個Redux的action creator。同時,一個影對象會被造出併合併到組件的props中。這個影對象會包含mapDispatchToProps中一樣的function名,可是每個action creator都被dispatch包裹,因此就能夠直接調用。

注:whenMapDispatchToPropsIsObject只是返回(dispatch, options)=>()=>disptachedActions,而後會在selectorFactory裏面傳入dispatch和options,並根據dependsOnOwnProps調用,最後得到裏面的constant。最後會在mergeProps方法裏面,把state, constant, props進行合併

wrapMapToPropsFunc

這個方法主要是針對mapStateToProps和mapDispatchToProps是function的狀況。

mapStateToProps

若是傳入了這個function,那麼組件就會監聽Redux store的更新。一旦store發生更新,那麼mapStateToProps就會被調用。返回的結果必須是一個plain object,結果會被合併到組件的props中。若是沒有傳入這個對象,那麼組件就不會監聽Redux Store。若是ownProps在定義中做爲一個參數,那麼ownProps的值就是傳入組件的props,同時每次props發生改變,mapStateToProps就會被從新調用。(若是組件接受的props發生了淺層改變(shallowly changed),同時ownProps也做爲一個參數被傳入,那麼mapStateToProps就會被從新調用)

mapDispatchToProps

若是傳入的是function,那麼dispatch會被傳入到這個function中。你能夠按照本身的意願返回被dispatch綁定過的action creators.

兩個都有

Note: 在一切特殊狀況下,你可能須要對渲染性能有更多的掌控,mapDispatchToProps()和mapStateToProps()也能夠返回一個function。在這種狀況下,返回的function會被做爲真正的mapDispatchToProps(mapStateToProps)。這麼作,容許你能夠作一些記憶類的操做(應該是說,能夠記錄上一次的state和ownProps,在function裏面能夠作對比,減小沒必要要的改變)

代碼是這樣子的:

export function wrapMapToPropsFunc(mapToProps, methodName) {
  return function initProxySelector(dispatch, { displayName }) {
    const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      return proxy.dependsOnOwnProps
        ? proxy.mapToProps(stateOrDispatch, ownProps)
        : proxy.mapToProps(stateOrDispatch)
    }

    proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps) // 初始化時根據mapToProps來判斷是否須要ownProps

    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
      // 第一次調用的時候,會進到這裏
      proxy.mapToProps = mapToProps
      let props = proxy(stateOrDispatch, ownProps) // 先獲取mapToProps的返回值

      if (typeof props === 'function') { // 若是返回值是function,那麼符合文檔中說的特殊狀況
        proxy.mapToProps = props // 把這個props看成真正的mapToProps
        proxy.dependsOnOwnProps = getDependsOnOwnProps(props) // 根據新的props方法來更新是否須要ownProps
        props = proxy(stateOrDispatch, ownProps) // 獲取最終結果
      }

      if (process.env.NODE_ENV !== 'production') 
        verifyPlainObject(props, displayName, methodName) // 若是是非production環境下,判斷結果的類型

      return props
    }

    return proxy
  }
}

這裏的mapToProps就是map(State/Dispatch)ToProps的意思,這個代碼主要作了幾個工做:

  1. 判斷這個方法是否基於ownProps

  2. 第一次調用的時候(或者說connect方法初始化的時候),若是mapToProps返回的時一個function,那麼就把這個function看成真正的mapToProps

  3. 第一次調用的時候,檢查返回的對象是否是一個plain object, 若是不是,那麼就在非production環境下拋出一個異常,提示開發者

注: 從代碼裏面看出一點,就是當function返回function的狀況,其實dependsOnOwnProps並非根據外層的function來定的,而是根據返回的function而定的。並且,像文檔中所說,他的主要做用是作對比(和上一個state, ownProps),因此我猜代碼應該像這個樣子

function mapStateToProps(state, ownProps){ // ownProps可選
    let oldState = state, oldProps = ownProps, lastState;
    return function(state, ownProps){ // ownProps可選
      // 在這裏對比當前state, ownProps和以前的oldState, oldProps,來生成新的state,或者直接用以前的state
      let ret = {};
      if(!lastState) {
        lastState = state; // do some computation here.
      } else if(!shallowEqual(state, oldState) || !shallowEqual(oldProps, ownProps)) {
        lastState = state; // do some computation here
      }
      
      oldState = state;
      oldProps = ownProps;

      return lastState;
    }
  }

同時,真正是否渲染根據ownProps改變,是基於內層的function來定的。因此說:

dependsOnOwnProps爲false

function mapStateToProps(state, ownProps){
    return function(state){ }
  }

dependsOnOwnProps爲true

function mapStateToProps(state){
    return function(state, ownProps){ }
  }

mapDispatchToProps.js

這個JS中提供了三個方法,分別應對mapDispatchToProps是function,object和null的狀況。

whenMapDispatchToPropsIsFunction

export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
  return (typeof mapDispatchToProps === 'function')
    ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
    : undefined
}

當mapDispatchToProps是function的時候,用wrapMapToPropsFunc來進行調用。最後返回的應該是(dispatch, {displayName})=>(dispatch, ownProps)=>binded Action Creators

whenMapDispatchToPropsIsMissing

export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
    return (!mapDispatchToProps)
      ? wrapMapToPropsConstant(dispatch => ({ dispatch }))
      : undefined
  }

當mapDispatchToProps是null的時候,調用wrapMapToPropsConstant(dispatch => ({ dispatch })),這裏這麼作的目的是,只把dispatch綁定到props上面。這裏返回的是(dispatch, options)=>()=>{ dispatch }

whenMapDispatchToPropsIsObject

export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
  return (mapDispatchToProps && typeof mapDispatchToProps === 'object')
    ? wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch))
    : undefined
}

當mapDispatchToProps是object的時候,調用wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch))進行包裝,根據以前對wrapMapToPropsConstant的介紹,這裏返回的是(dispatch, options)=>()=>binded Action Creators

最後的export default

export default [
  whenMapDispatchToPropsIsFunction,
  whenMapDispatchToPropsIsMissing,
  whenMapDispatchToPropsIsObject
]

這裏寫的頗有趣,也很值得借鑑。先看一下調用的地方:

const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
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}.`)
  }
}

這裏mapDispatchToPropsFactories是以前的數組。因爲這三個方法若是類型判斷不正確,就會返回undefined,因此這裏的match經過循環的方法來進行判斷和解析。若是result存在,那麼就說明判斷經過,就返回。若是result不存在,那麼就檢查下一個。

若是按照平時的寫法,我確定會用不少的if來進行判斷,這樣子反而增長了代碼量,並且不美觀。好比這樣:

export default function(mapDispatchToProps){
  let result = undefined;
  
  result = whenMapDispatchToPropsIsFunction(mapDispatchToProps);
  if(!result) result = whenMapDispatchToPropsIsMissing(mapDispatchToProps);
  if(!result) result = whenMapDispatchToPropsIsObject(mapDispatchToProps);

  return result;
}

和源碼裏的比較,感受仍是源碼裏寫的好看,有趣。

mapStateToProps.js

這個JS中提供了兩個方法,分別應對mapDispatchToProps是function和null的狀況。

whenMapStateToPropsIsFunction

export function whenMapStateToPropsIsFunction(mapStateToProps) {
  return (typeof mapStateToProps === 'function')
    ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
    : undefined
}

當mapStateToProps是function的時候,進行封裝。這裏返回的結果是(dispatch, {displayName})=>(state, ownProps)=>state result

whenMapStateToPropsIsMissing

export function whenMapStateToPropsIsMissing(mapStateToProps) {
  return (!mapStateToProps)
    ? wrapMapToPropsConstant(() => ({}))
    : undefined
}

這裏,當mapStateToProps是null的時候,返回結果是(dispatch, {displayName})=>(state, ownProps)=>{}。之因此這裏返回的是{},而不是undefined後者null,是由於便於之後在mergeProps的時候不須要再去檢查undefined和null的狀況。

export default

export default [
  whenMapStateToPropsIsFunction,
  whenMapStateToPropsIsMissing
]

mergeProps.js

這個JS中提供了兩個方法,分別應對mergeProps是function和null的狀況。

插一段mergeProps的文檔:

若是這個值不是undefined,那麼它會接受mapStateToProps(), mapDispatchToProps()和父組件傳入的props。這個方法返回的plain object會被看成組件的props傳給組件。你能夠在這個方法裏面只選擇部分props返回,或者根據傳入的props給action creators綁定一些參數。若是沒有傳這個值,默認值是Object.assign({}, ownProps, stateProps, dispatchProps)

defaultMergeProps

export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
  return { ...ownProps, ...stateProps, ...dispatchProps }
}

默認的mergeProps方法。和文檔中說的同樣,不用解釋。

wrapMergePropsFunc

export function wrapMergePropsFunc(mergeProps) {
  return function initMergePropsProxy(
    dispatch, { displayName, pure, areMergedPropsEqual } // 後面的其實都是option裏面東西
  ) {
    let hasRunOnce = false // 第一次不用檢查,直接賦值,因此有一個flag
    let mergedProps // 記錄props,用於賦值和對比

    return function mergePropsProxy(stateProps, dispatchProps, ownProps) {
      const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps)

      if (hasRunOnce) {
        if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))
          mergedProps = nextMergedProps

      } else {
        hasRunOnce = true
        mergedProps = nextMergedProps

        if (process.env.NODE_ENV !== 'production')
          verifyPlainObject(mergedProps, displayName, 'mergeProps')
      }

      return mergedProps
    }
  }
}

在這裏先說一下pure這個option

pure是connect的第四個參數option中的一項,默認值是true。在這裏的定義是,一個組件是pure的話,就說明這個組件並不依賴於除了props和Redux store的state意外的任何輸入和狀態。若是pure是true,當相關的props和state通過對比,並無發生改變的話,那麼就不去調用mapStateToProps,mapDispatchToProps和mergeProps。反之,不管是否改變,都會調用這些方法更新props。(注: 這裏的通過對比,在這裏方法裏是areMergedPropsEqual這個對比的方法。另外還有areStatesEqual, areOwnPropsEqual, areStatePropsEqual都是在option中定義)

這裏作了幾件事情:

  1. 第一次調用的時候,不進行是否改變的判斷,同時檢查返回值的格式

  2. 第二次之後的調用,都會進行pure和改變的判斷,若是改變了,才修改mergedProps值,減小了沒必要要的渲染

export default

export function whenMergePropsIsFunction(mergeProps) {
  return (typeof mergeProps === 'function')
    ? wrapMergePropsFunc(mergeProps)
    : undefined
}

export function whenMergePropsIsOmitted(mergeProps) {
  return (!mergeProps)
    ? () => defaultMergeProps
    : undefined
}

export default [
  whenMergePropsIsFunction,
  whenMergePropsIsOmitted
]

和以前的幾個js同樣,包含isFunction和isOmitted兩個方法。

一點總結

  1. 能夠利用function.length來判斷這個function在定義的時候有幾個參數。能夠用來判斷某些參數是否須要傳入,或許能夠減小沒必要要的計算。可是,須要注意的是,當function中使用arguments,或者function(...args){}的時候,雖然內部可能會使用多個參數,可是length返回0,沒法經過length屬性進行判斷。

  2. 因爲stateProps和dispatchProps是否根據ownProps來進行更新是根據function.length來定的,因此若是不須要,就不要在定義的時候加上這個參數。

  3. 須要處理一個對象,這個對象可能有多種類型的時候,咱們能夠不選擇在方法寫if..else來判斷類型,能夠像mapDispatchToProps, mapStateToProps同樣,返回一個function的數組,每一個function裏面判斷是不是符合的類型,是的話,按這個類型處理。不是的話,返回undefined。簡單,好用。

  4. 當一個系統接受的同一個參數多是多種不一樣的類型,該怎麼辦?咱們並不指望在裏面使用過多的typeof, if...else來進行判斷,代碼會顯得比較囉嗦臃腫。咱們能夠考慮把全部類型都轉化成爲統一的一個類型。就像這裏的wrapMapToProps同樣,把Object,function, null都轉成function,之後就不須要多餘處理類型了

下篇:《Connect工具類篇(2)》

相關文章
相關標籤/搜索