react-redux 源碼解讀之connect的mapStateToProps

相關文件

react-redux/src/connect/connect.js  --入口文件:接受傳入的mapStateToProps
react-redux/src/connect/mapStateToProps.js --對傳入mapStateToProps進行空值、或function判斷處理
react-redux/src/connect/wrapMapToProps.js --包裝mapStateToProps和mapDispitchToProps
react-redux/src/components/connectAdvanced.js -- 不做處理,轉發一下
react-redux/src/connect/selectorFactory.js  --調用mapStateToProps的地方

下面對以上相關文件中,關於mapStateToProps部分進行講解。

mapStateToProps從定義到最終調用的過程

我們在各個組件中使用connect,並定義mapStateToProps。

connect.js

connect(mapStateToProps,…) --接受各組件定義的mapStateToProps:
幷包裝mapStateToProps,得到initMapStateToProps,並傳給 connectAdvanced.js:

//connect.js
 const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') ---包裝mapStateToProps
 connectHOC(..., { initMapStateToProps,.... })

connectAdvanced.js

connectOptions包含initMapStateToProps.
connectAdvanced.js接收connectOptions (initMapStateToProps),不做任何改變,直接轉發,傳給selectorFactory.js :

//connectAdvanced.js
connectAdvanced(... {...connectOptions } = {})  ---connectOptions包含initMapStateToProps;
const selectorFactoryOptions = { ...connectOptions,...}
selectorFactory(... selectorFactoryOptions)

selectorFactory.js

selectorFactory.js接收initMapStateToProps,並執行一次它,得到最終每次調用的mapStateToProps

//selectorFactory.js
const mapStateToProps = initMapStateToProps(dispatch, options)
//以後每次都在handleNewState方法中調用
mapStateToProps(state, ownProps)

以上過程我們可以看到,其實都是針對initMapStateToProps進行,中間有些過程只是直接轉發,並沒有操作改變mapStateToProps。

簡單點,可以將上一節過程簡化爲:

//這裏的mapStateToProps 爲 各組件定義的mapStateToProps
 const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') 
 //這裏的mapStateToProps 爲 各組件定義的mapStateToProps經過connect框架轉化後最終執行的mapStateToProps
 const mapStateToProps = initMapStateToProps(dispatch, options)

connect框架爲了達到使用方便,允許用戶靈活定義mapStateToProps,框架會對用戶定義的mapStateToProps經過一系列轉化,變成可用的mapStateToProps。

讀懂initMapStateToProps纔是理解mapStateToProps的關鍵。

initMapStateToProps

爲方便理解,我們只討論組件內定義的mapStateToProps 非空,且爲function的情況

//connect.js
const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') 
....
function match(arg, factories, name) {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg)
    if (result){
        return result
    }
  }
}

mapStateToPropsFactories是一個這樣的數組:

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

export function whenMapStateToPropsIsMissing(mapStateToProps) {
  return (!mapStateToProps)
    ? wrapMapToPropsConstant(() => ({}))
    : undefined
}
export default [
  whenMapStateToPropsIsFunction,
  whenMapStateToPropsIsMissing
]

以上結合match使用,妙的地方在於 :
match是使用length–進行for遍歷,whenMapStateToPropsIsMissing是第一個被match處理的。
whenMapStateToPropsIsMissing只處理mapStateToProps爲空情況,當mapStateToProps有值的時候,返回一個undefined,
從而通過match讓whenMapStateToPropsIsFunction來處理,
match函數通過return終止操作

於是
const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, ‘mapStateToProps’)
相當於
const initMapStateToProps = wrapMapToPropsFunc(mapStateToProps, ‘mapStateToProps’);

wrapMapToPropsFunc方法爲:

//wrapMapToProps.js
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)
        }
        return proxy
    }
}

由此可見
wrapMapToPropsFunc(mapStateToProps, ‘mapStateToProps’)
相當於
initProxySelector方法。

因此可以認爲:
const initMapStateToProps = initProxySelector;

上文說到而我們最終使用的mapStateToProps:
const mapStateToProps = initMapStateToProps(dispatch, options);

因此
最終的mapStateToProps函數其實就是initProxySelector()執行後得到的結果,我們可以認爲以下是相等的:
//最終使用到的mapStateToProps
const mapStateToProps = proxy;

由此,我們看到,在各個組件中,在connect中定義的mapStateToProps其實將轉化爲代理方法proxy。

以上過程我們簡化爲:
const initMapStateToProps = wrapMapToPropsFunc(mapStateToProps, ‘mapStateToProps’);
const mapStateToProps = initMapStateToProps()=proxy;
所以整個過程都基於高階函數wrapMapToPropsFunc進行。

wrapMapToPropsFunc

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 = true
    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
      proxy.mapToProps = mapToProps
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      let props = proxy(stateOrDispatch, ownProps)
      return props
    }

    return proxy
  }
}

wrapMapToPropsFunc的多層閉包函數設計方式

wrapMapToPropsFunc這個函數,其實是一個嵌套了三層的高階閉包函數,
函數返回一個函數,返回的函數又返回一個函數,
也就是說
const proxy = wrapMapToPropsFunc(mapToProps, methodName)(dispatch, { displayName })
proxy是最終調用的mapStateToProps;

爲什麼使用多層閉包函數設計方式,這裏的原因主要在於傳值,下一次return出來的閉包函數,執行時能夠始終拿到對應母函數傳過來的值。
比如:

initProxySelector = wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps');

閉包函數initProxySelector每次執行的時候就可以使用mapStateToProps, ‘mapStateToProps’;

proxy = initProxySelector(dispatch, { displayName }) ;

閉包函數proxy執行時,就可以始終使用dispatch, { displayName };
所以多層閉包的使用,最主要還是爲了能夠多一次傳值 給最終返回閉包的函數 使用。
多層閉包函數設計方式也是一個很妙的js設計方法。

wrapMapToPropsFunc分析

明白了多層閉包設計的目的,我們針對wrapMapToPropsFunc多層執行的用意:
const proxy = wrapMapToPropsFunc(mapToProps, methodName)(dispatch, { displayName })

wrapMapToPropsFunc(mapToProps, methodName)

它就是爲了給後面返回的閉包函數提供參數數據mapToProps, methodName;
這裏的mapToProps就是組件內定義的mapStateToProps;

initProxySelector(dispatch, { displayName })

initProxySelector:

//wrapMapToProps.js 
return function initProxySelector(dispatch, { displayName }) {
    const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
    }
    proxy.dependsOnOwnProps = true
    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
    }
    return proxy
  }

initProxySelector(dispatch, { displayName })做了三件事情:
1、提供參數變量 dispatch, { displayName })供以後的閉包函數使用;
2、設置proxy.dependsOnOwnProps = true
3、定義proxy

proxy

最後再看重頭戲:

const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      return proxy.dependsOnOwnProps
        ? proxy.mapToProps(stateOrDispatch, ownProps)
        : proxy.mapToProps(stateOrDispatch)
    }
    proxy.dependsOnOwnProps = true
    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
      proxy.mapToProps = mapToProps
      //getDependsOnOwnProps判斷mapToProps方法有幾個形參,如果爲一個,就爲false,如果有兩個形參爲true;
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      let props = proxy(stateOrDispatch, ownProps)
     return props
    }

因爲 proxy.mapToProps = mapToProps這樣賦值過後,在mapToPropsProxy(stateOrDispatch, ownProps)執行第一次後,
以後每次執行,代碼等同與:

const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
        return mapToProps(stateOrDispatch, ownProps)
    }

也因此 proxy 完全等同於 函數mapToProps;
而mapToProps就是組件中定義的mapStateToProps

//組件中定義的mapStateToProps
proxy === mapStateToProps

proxy這樣的設計有幾點用意:
1、通過傳入的stateOrDispatch形參的個數,設置proxy.dependsOnOwnProps;
控制是否傳入ownProps參數。
2、設計靈活,根據不同的判斷調用不同的函數或傳入不同的參數,後期還可以加需求
3、代碼更直觀,美觀

經過上面繞了一大圈一大圈,其實你發現 上面四個js什麼都沒做,
將mapStateToProps傳進去後,又原封不動返回出來了mapStateToProps;
其實這種模式下,能夠包裝mapStateToProps,讓用戶使用更加簡潔定義mapStateToProps,然後框架層面進行包裝。

對於proxy的理解舉例

    function fn(a,b) {
        console.log(a)
        console.log(b)
        return {...a}
    }
   function wrapMapToPropsFunc(mapToProps) {
        const proxy = function mapToPropsProxy(stateOrDispatch) {
            return proxy.dependsOnOwnProps
                ? proxy.mapToProps(stateOrDispatch,5)
                : proxy.mapToProps(stateOrDispatch)
        }
        proxy.dependsOnOwnProps = true
        proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch) {
            console.log(1)
            proxy.mapToProps = mapToProps
            //proxy.mapToProps重新定義爲mapToProps也就是fn,以後再次執行將只執行fn;
            //不會再執行detectFactoryAndVerify,這種設計模式下,detectFactoryAndVerify只會執行一遍,
            // 後期每次執行都只是執行fn
            //所以這裏都abc完全等於fn
            proxy.dependsOnOwnProps = false
            //由於fn是一個返回對象都函數,所以這裏需要執行 本身 返回一次對象
            let props = proxy(stateOrDispatch)
            return props
        }
        return proxy
    }

    var abc = wrapMapToPropsFunc(fn);
    abc();//執行detectFactoryAndVerify
    abc();//不再執行detectFactoryAndVerify
    // 所以這裏都abc完全等於fn,proxy也安全等於fn
    // 以上設計,會得到如下結果:
    // todo abc=proxy=fn

proxy進階分析

mapStateToProps可以定義爲如下形式,當爲此種模式時每次執行mapStateToProps可以獲得初始時的initstate,

const mapStateToPropsCreator = (state)=>{
    return ({addRedux:state.addRedux})
};
const mapStateToProps = (initstate,ownprops)=>{
    return mapStateToPropsCreator;
};

這都是在proxy上定義:

 const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      ....
    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
      ....
      if (typeof props === 'function') {
        proxy.mapToProps = props
        proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
        props = proxy(stateOrDispatch, ownProps)
      }
    ....
    }
    return  proxy
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章