注:這篇文章只是講解React Redux這一層,並不包含Redux部分。Redux有計劃去學習,等之後學習了Redux源碼之後再作分析;
注:代碼基於如今 (2016.12.29) React Redux 的最新版本 (5.0.1)。javascript
這裏講的實際上是connect方法的基礎,原本是準備先把connectAdvanced和Provider寫完,再專門寫connect和相關方法的,可是發現connectAdvanced也用到了不少這些基礎方法,因此就停下來,寫下面的這些東西。html
以前沒有上過代碼結構圖,這裏貼張圖上來(5.0.1版本)
java
React Redux在最近的幾個版本中對代碼作了拆分和優化,以前看4.x的代碼的時候,connect下面全部的方法都在一個文件裏面,並且邏輯也不夠清晰。當時原本想吐槽他們的源代碼的,可是升級到5.x之後,就發現代碼清晰不少,只不過代碼邏輯複雜度更上一層樓。。。react
單獨提出來這個屬性,作一個簡單的說明。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
這段代碼主要的做用就是判斷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
function mapStateToProps(state){}
,那麼mapStateToProps.length === 1,不須要ownProps
function mapStateToProps(state, ownProps){}
, 那麼mapStateToProps.length === 2, 須要ownProps
function mapStateToProps(){ arguments[0]; }
或者 function mapStateToProps(...args){}
, 那麼mapStateToProps.length === 0, 因爲沒法經過定義判斷是否須要ownProps,因此默認是須要
若是以前已經設置過了,那麼就不須要設置了,重用Boolean(mapToProps.dependsOnOwnProps)
首先,根據文檔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進行合併
這個方法主要是針對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的意思,這個代碼主要作了幾個工做:
判斷這個方法是否基於ownProps
第一次調用的時候(或者說connect方法初始化的時候),若是mapToProps返回的時一個function,那麼就把這個function看成真正的mapToProps
第一次調用的時候,檢查返回的對象是否是一個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){ } }
這個JS中提供了三個方法,分別應對mapDispatchToProps是function,object和null的狀況。
export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) { return (typeof mapDispatchToProps === 'function') ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps') : undefined }
當mapDispatchToProps是function的時候,用wrapMapToPropsFunc來進行調用。最後返回的應該是(dispatch, {displayName})=>(dispatch, ownProps)=>binded Action Creators
export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) { return (!mapDispatchToProps) ? wrapMapToPropsConstant(dispatch => ({ dispatch })) : undefined }
當mapDispatchToProps是null的時候,調用wrapMapToPropsConstant(dispatch => ({ dispatch }))
,這裏這麼作的目的是,只把dispatch綁定到props上面。這裏返回的是(dispatch, options)=>()=>{ dispatch }
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 [ 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; }
和源碼裏的比較,感受仍是源碼裏寫的好看,有趣。
這個JS中提供了兩個方法,分別應對mapDispatchToProps是function和null的狀況。
export function whenMapStateToPropsIsFunction(mapStateToProps) { return (typeof mapStateToProps === 'function') ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') : undefined }
當mapStateToProps是function的時候,進行封裝。這裏返回的結果是(dispatch, {displayName})=>(state, ownProps)=>state result
export function whenMapStateToPropsIsMissing(mapStateToProps) { return (!mapStateToProps) ? wrapMapToPropsConstant(() => ({})) : undefined }
這裏,當mapStateToProps是null的時候,返回結果是(dispatch, {displayName})=>(state, ownProps)=>{}
。之因此這裏返回的是{},而不是undefined後者null,是由於便於之後在mergeProps的時候不須要再去檢查undefined和null的狀況。
export default [ whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing ]
這個JS中提供了兩個方法,分別應對mergeProps是function和null的狀況。
插一段mergeProps的文檔:
若是這個值不是undefined,那麼它會接受mapStateToProps(), mapDispatchToProps()和父組件傳入的props。這個方法返回的plain object會被看成組件的props傳給組件。你能夠在這個方法裏面只選擇部分props返回,或者根據傳入的props給action creators綁定一些參數。若是沒有傳這個值,默認值是
Object.assign({}, ownProps, stateProps, dispatchProps)
export function defaultMergeProps(stateProps, dispatchProps, ownProps) { return { ...ownProps, ...stateProps, ...dispatchProps } }
默認的mergeProps方法。和文檔中說的同樣,不用解釋。
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中定義)
這裏作了幾件事情:
第一次調用的時候,不進行是否改變的判斷,同時檢查返回值的格式
第二次之後的調用,都會進行pure和改變的判斷,若是改變了,才修改mergedProps值,減小了沒必要要的渲染
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兩個方法。
能夠利用function.length來判斷這個function在定義的時候有幾個參數。能夠用來判斷某些參數是否須要傳入,或許能夠減小沒必要要的計算。可是,須要注意的是,當function中使用arguments,或者function(...args){}的時候,雖然內部可能會使用多個參數,可是length返回0,沒法經過length屬性進行判斷。
因爲stateProps和dispatchProps是否根據ownProps來進行更新是根據function.length來定的,因此若是不須要,就不要在定義的時候加上這個參數。
須要處理一個對象,這個對象可能有多種類型的時候,咱們能夠不選擇在方法寫if..else來判斷類型,能夠像mapDispatchToProps, mapStateToProps同樣,返回一個function的數組,每一個function裏面判斷是不是符合的類型,是的話,按這個類型處理。不是的話,返回undefined。簡單,好用。
當一個系統接受的同一個參數多是多種不一樣的類型,該怎麼辦?咱們並不指望在裏面使用過多的typeof, if...else來進行判斷,代碼會顯得比較囉嗦臃腫。咱們能夠考慮把全部類型都轉化成爲統一的一個類型。就像這裏的wrapMapToProps同樣,把Object,function, null都轉成function,之後就不須要多餘處理類型了