注意:文章很長,只想瞭解邏輯而不深刻的,能夠直接跳到 總結部分。
首先,從它暴露對外的API
開始前端
ReactReduxContext /* 提供了 React.createContext(null) */ Provider /* 一個儲存數據的組件,渲染了ContextProvider,內部調用redux中store.subscribe 訂閱數據,每當redux中的數據變更,比較新值與舊值,判斷是否從新渲染 */ connect /* 一個高階組件,第一階傳入對數據處理方法,第二階傳入要渲染的組件 內部處理了: 1. 對參數的檢查 2. 對傳入的數據處理方法進行處理 (沒傳怎麼處理,傳了提供什麼參數,傳的類型不一樣怎麼處理,結果如何比較等等) 3. 靜態方法轉移 4. 對渲染組件的傳遞(傳遞給connectAdvanced) */ connectAdvanced /* 保存每一次執行的數據,執行connect定義的方案和邏輯,新舊數據對比(全等對比),渲染組件 這裏做爲公開API,若是咱們去使用,那麼connect裏面的邏輯就須要咱們自定義了。 */
如今對它的大概工做範圍有了解後,咱們能夠開始沿着執行順序分析。react
咱們使用時,當寫完了redux的reducer
, action
, bindActionCreators
, combineReducers
, createStore
這一系列內容後,
咱們獲得了一個store
git
會先使用<Provider store={store}
包裹住根組件。github
這時,Provider
組件開始工做redux
componentDidMount() { this._isMounted = true this.subscribe() }
第一次加載,須要執行subscribe
數組
subscribe
是什麼呢,就是對redux
的store
執行subscribe
一個自定義函數,
這樣,每當數據變更,這個函數便會執行閉包
subscribe() { const { store } = this.props // redux 的 store 訂閱 // 訂閱後,每當state改變 則自動執行這個函數 this.unsubscribe = store.subscribe(() => { // store.getState() 獲取最新的 state const newStoreState = store.getState() // 組件未加載,取消 if (!this._isMounted) { return } // 比較state是否相等,全等的不更新 this.setState(providerState => { if (providerState.storeState === newStoreState) { return null } return { storeState: newStoreState } }) }) /* ... */ }
看到嗎,這個自定義函數很是簡單,每次收到數據,進行全等比較,不等則更新數據。app
這個組件的另2個生命週期函數:ide
componentWillUnmount() { if (this.unsubscribe) this.unsubscribe() this._isMounted = false } componentDidUpdate(prevProps) { // 比較store是否相等,若是相等則跳過 if (this.props.store !== prevProps.store) { // 取消訂閱以前的,再訂閱如今的(由於數據(store)不一樣了) if (this.unsubscribe) this.unsubscribe() this.subscribe() } }
這2段的意思就是,每當數據變了,就取消上一次數據的訂閱,在訂閱本次的數據,
當要銷燬組件,取消訂閱。函數
一段題外話(可跳過):
這個邏輯用
Hooks
的useEffect
簡直完美匹配!useEffect(()=>{ subscribe() return ()=>{ unSubscribe() } },props.data)這段的意思就是,當
props.data
發生改變,執行unSubscribe()
,再執行subscribe()
。邏輯徹底一致有沒有!
最後的render
:
這裏Context
就是React.createContext(null)
<Context.Provider value={this.state}> {this.props.children} </Context.Provider>
到這裏我稱爲react-redux
的第一階段。
一個小總結,第一階段就作了1件事:
定義了Provider
組件,內部訂閱了store
。
到主菜了,先看它的export
export default createConnect()
一看,咱們應該有個猜想,這貨createConnect
是個高階函數。
看看它的參數吧。
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) { /* ... */ }
題外話:一個編寫默認對象內部含有默認值的方法
function a({x=1,y=2}={}){} a() // x:1,y:2 a({}) // x:1,y:2 a({x:2,z:5}) //x:2,y:2
這裏先說明一下它的參數,後面讀起來會很順。
connectHOC: 一個重要組件,用於執行已肯定的邏輯,渲染最終組件,後面會詳細說。 mapStateToPropsFactories: 對 mapStateToProps 這個傳入的參數的類型選擇一個合適的方法。 mapDispatchToPropsFactories: 對 mapDispatchToProps 這個傳入的參數的類型選擇一個合適的方法。 mergePropsFactories: 對 mergeProps 這個傳入的參數的類型選擇一個合適的方法。 selectorFactory: 以上3個只是簡單的返回另外一個合適的處理方法,它則執行這些處理方法,而且對結果定義瞭如何比較的邏輯。
可能有點繞,但react-redux
就是這麼一個個高階函數組成的,selectorFactory
後面會詳細說。
首先咱們再次肯定這3個名字很長,實際很簡單的函數(源碼這裏不放了)
mapStateToPropsFactories
mapDispatchToPropsFactories
mergePropsFactories
它們只是判斷了參數是否存在,是什麼類型,而且返回一個合適的處理方法,它們並無任何處理邏輯。
const MyComponent=connect((state)=>state.articles})
這裏我只定義了mapStateToProps
,而且是個function
,那麼mapStateToPropsFactories
就會返回一個
處理function
的方法。
我沒有定義mapDispatchToProps
,那麼mapDispatchToPropsFactories
檢測不到參數,
則會提供一個默認值dispatch => ({ dispatch })
,返回一個處理非function
(object)的方法。
那麼處理邏輯是誰定義呢?
wrapMapToProps.js
這個文件內部作了如下事情:
object
的方法(簡單的返回便可,由於最終目的就是要object)。函數
和高階函數
(執行2次)的方法,這個方法比上面的複雜在於它須要檢測參數是否訂閱了ownProps
。檢測方法很簡單,就是檢查參數的length
(這裏dependsOnOwnProps
是上一次檢查的結果,若是存在則不須要再次檢查)
export function getDependsOnOwnProps(mapToProps) { return mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length !== 1 }
回到connect,繼續往下看
export function createConnect({ /* 上面所講的參數 */ } = {}) { return function connect( mapStateToProps, mapDispatchToProps, mergeProps, { pure = true, areStatesEqual = strictEqual, areOwnPropsEqual = shallowEqual, areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions } = {} ) { /* ... */ } }
已經到了咱們傳遞參數的地方,前3個參數意思就不解釋了,最後的參數options
areStatesEqual = strictEqual, // ===比較 areOwnPropsEqual = shallowEqual, // 淺比較 areStatePropsEqual = shallowEqual, // 淺比較 areMergedPropsEqual = shallowEqual, // 淺比較
它們用在selectorFactory
這個比較數據結果的方法內部。
繼續往下看
export function createConnect({ /* 上面已講 */ } = {}) { return function connect( /* 上面已講 */ ) { const initMapStateToProps = match( mapStateToProps, mapStateToPropsFactories, 'mapStateToProps' ) const initMapDispatchToProps = match( mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps' ) const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
這裏定義了3個變量(函數),match
的做用是什麼?
以mapStateToProps
舉例來講,
由於上面也說了,mapStateToPropsFactories
裏面有多個方法,須要找到一個適合mapStateToProps
的,match
就是幹這事了。
match
方法內部遍歷mapStateToPropsFactories
全部的處理方法,任何一個方法可以匹配參數mapStateToProps
,便被match
捕獲返回,
若是一個都找不到則報錯提示參數配置錯誤。
如今這3個變量定義明確了,都是對應的參數的合適的處理方法。
至此,咱們已經完成了第二階段,
作個小總結,第二階段作了哪些事:
connect
接收了對參數處理方案(3個...Factories
)。connect
接收了參數的結果比較方案(selectFactory
)connect
接收了參數(mapStateToProps
,mapDispatchToProps
,mergeProps
,options
)。are...Equal
,其實就是全等比較
和淺比較
)。前2個階段都是定義階段,接下來須要咱們傳入自定義組件,也就是最後一個階段
connect(...)(Component)
接着看connect
源碼
export function createConnect({ /* 上面已講 */ } = {}) { return function connect( /* 上面已講 */ ) { /* 上面已講 */ return connectHOC(selectorFactory, { // 方法名稱,用在錯誤提示信息 methodName: 'connect', // 最終渲染的組件名稱 getDisplayName: name => `Connect(${name})`, shouldHandleStateChanges: Boolean(mapStateToProps), // 如下是傳遞給 selectFactory initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // any extra options args can override defaults of connect or connectAdvanced ...extraOptions }) } }
這裏執行了connectHOC()
,傳遞了上面已經講過的參數,而connectHOC = connectAdvanced
所以咱們進入最後一個對外API
,connectAdvanced
connectAdvanced
函數,以前也提過,就是一個執行、組件渲染和組件更新的地方。
它裏面沒有什麼新概念,都是將咱們上面講到的參數進行調用,最後根據結果進行渲染新組件。
仍是從源碼開始
export default function connectAdvanced( selectorFactory, { // 執行後做用於connect這個HOC組件名稱 getDisplayName = name => `ConnectAdvanced(${name})`, // 用於錯誤提示 methodName = 'connectAdvanced', // 有REMOVED標誌,這裏不關注 renderCountProp = undefined, // 肯定connect這個HOC是否訂閱state變更,好像已經沒有用到了 shouldHandleStateChanges = true, // 有REMOVED標誌,這裏不關注 storeKey = 'store', // 有REMOVED標誌,這裏不關注 withRef = false, // 是否經過 forwardRef 暴露出傳入的Component的DOM forwardRef = false, // React的createContext context = ReactReduxContext, // 其他的(比較方法,參數處理方法等)將會傳遞給上面的 selectFactory ...connectOptions } = {} ) { /* ... */ }
參數也沒什麼特別的,有一個forwardRef
做用就是能獲取到咱們傳入的Component
的DOM。
這裏也不深刻。
接着看
export default function connectAdvanced( /* 上面已講 */ ) { /* ...對參數的一些驗證和提示哪些參數已經做廢... */ // 定義Context const Context = context return function wrapWithConnect(WrappedComponent) { /* ...檢查 WrappedComponent 是否符合要求... */ /* ...獲取傳入的WrappedComponent的名稱... */ /* ...經過WrappedComponent的名稱計算出當前HOC的名稱... */ /* ...獲取一些上面的參數(沒有新的參數,都是以前見過的)... */ // Component就是React.Component let OuterBaseComponent = Component let FinalWrappedComponent = WrappedComponent // 是否純組件 if (pure) { OuterBaseComponent = PureComponent } /* 定義 makeDerivedPropsSelector 方法,做用後面講 */ /* 定義 makeChildElementSelector 方法,做用後面講 */ /* 定義 Connect 組件,做用後面講 */ Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName /* ...若是是forWardRef 爲true的狀況,此處不深刻... */ // 靜態方法轉換 return hoistStatics(Connect, WrappedComponent) } }
這一段特別長,所以我將不過重要的直接用註釋說明了它們在作什麼,具體代碼就不放了(不重要)。
而且定義了3個新東西,makeDerivedPropsSelector
,makeChildElementSelector
,Connect
。
先看最後一句hoistStatics
就是hoist-non-react-statics
,它的做用是將組件WrappedComponent
的全部非React
靜態方法傳遞到Connect
內部。
那麼最終它仍是返回了一個Connect
組件。
這個組件已是咱們寫了完整connect(...)(Component)
的返回值了,因此能肯定,只要調用<Connect />
,就能渲染出一個新的組件出來。
所以它的功能就是肯定是否重複更新組件和肯定到底更新什麼?
看一個組件,從constructor
看起
class Connect extends OuterBaseComponent { constructor(props) { super(props) /* ...提示一些無用的參數...*/ this.selectDerivedProps = makeDerivedPropsSelector() this.selectChildElement = makeChildElementSelector() this.renderWrappedComponent = this.renderWrappedComponent.bind(this) } /* ... */ }
綁定了一個方法,看名字是render的意思,先無論它。
執行了2個函數。
Connect
組件還沒完,這裏先放着,咱們先看makeDerivedPropsSelector
和makeChildElementSelector
function makeDerivedPropsSelector() { // 閉包儲存上一次的執行結果 let lastProps let lastState let lastDerivedProps let lastStore let sourceSelector return function selectDerivedProps(state, props, store) { // props和state都和以前相等 直接返回上一次的結果 if (pure && lastProps === props && lastState === state) { return lastDerivedProps } // 當前store和lastStore不等,更新lastStore if (store !== lastStore) { lastStore = store // 終於調用 selectorFactory 了 sourceSelector = selectorFactory( store.dispatch, selectorFactoryOptions ) } // 更新數據 lastProps = props lastState = state // 返回的就是最終的包含全部相應的 state 和 props 的結果 const nextProps = sourceSelector(state, props) // 最終的比較 if (lastDerivedProps === nextProps) { return lastDerivedProps } lastDerivedProps = nextProps return lastDerivedProps } }
大概的說,makeDerivedPropsSelector
的執行,先判斷了當前傳入的props(組件的props)
和state(redux傳入的state)
跟之前的是否全等,若是全等就不須要更新了;
若是不等,則調用了高階函數selectFactory
,而且得到最終數據,最後再判斷最終數據和以前的最終數據是否全等。
爲何第一次判斷了,還要判斷第二次,並且都是===
判斷?
由於第一次獲取的state
是redux
傳入的,是整個APP的全部數據,它們不等說明有組件更新了,但不肯定是不是當前組件;
第二次比較的是當前組件的最新數據和之前數據對比。
如今,咱們知道selectFactory
的做用是獲取當前組件的的最新數據,深刻源碼看看。
export default function finalPropsSelectorFactory( // redux store的store.dispatch dispatch, // 3種已經肯定了的處理方法 { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options } ) { // 返回一個針對用戶傳入的類型的解析函數 // 例如 mapStateToProps 若是是function,那麼就返回proxy,proxy能夠判斷是否須要ownProps,而且對高階函數的 mapStateToProps 進行2次處理, // 最終確保返回一個plainObject,不然報錯 const mapStateToProps = initMapStateToProps(dispatch, options) const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options) if (process.env.NODE_ENV !== 'production') { verifySubselectors( mapStateToProps, mapDispatchToProps, mergeProps, options.displayName ) } const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory // 默認pure問題true,所以執行 pureFinalPropsSelectorFactory(...) return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options ) }
參數就不說了,看註釋。
如下3個,到底返回了什麼,源碼在wrapMapToProps.js
,上面也說過這個文件內部作了什麼事情。
const mapStateToProps = initMapStateToProps(dispatch, options) const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options)
這3個調用返回的一個函數,名字叫proxy
,這個proxy
一旦調用,
就能返回通過mapStateToProps
, mapDispatchToProps
, mergeProps
這3個參數處理事後的數據(plainObject
)。
接下來:
const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory // 默認pure問題true,所以執行 pureFinalPropsSelectorFactory(...) return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options )
返回了selectorFactory
的調用值,也就是pureFinalPropsSelectorFactory
(pure默認爲true)。
看pureFinalPropsSelectorFactory
,它的代碼很多,但邏輯很明瞭,大方向就是對比數據。
這裏關鍵的如何比較不列代碼,只用註釋講明白它的邏輯。
export function pureFinalPropsSelectorFactory( // 接受3個proxy方法 mapStateToProps, mapDispatchToProps, mergeProps, dispatch, // 接受3個比較方法 { areStatesEqual, areOwnPropsEqual, areStatePropsEqual } ) { /* ...定義變量保存以前的數據(閉包)... */ function handleFirstCall(firstState, firstOwnProps) { /* ...定義第一次執行數據比較的方法,也就是簡單的賦值給上面定義的閉包變量... */ } function handleNewPropsAndNewState() { /* 當state和props都有變更時的處理方法 */ } function handleNewProps() { /* 當state無變更,props有變更時的處理方法 */ } function handleNewState() { /* 當state有變更,props無變更時的處理方法 */ } // 後續數據比較的方法 function handleSubsequentCalls(nextState, nextOwnProps) { // 淺比較 const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) // 全等比較 const stateChanged = !areStatesEqual(nextState, state) // 更新數據 state = nextState ownProps = nextOwnProps // 當發生不相等的3種狀況(關鍵) if (propsChanged && stateChanged) return handleNewPropsAndNewState() if (propsChanged) return handleNewProps() if (stateChanged) return handleNewState() // 比較都相等,直接返回舊值 return mergedProps } return function pureFinalPropsSelector(nextState, nextOwnProps) { return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps) } }
上面的閉包變量儲存了上一次的數據,關鍵點就是當和這一次的數據比較後,若是處理更新。
react-redux
將它分爲3種狀況
state
和props
都相等。state
相等,props
不等。state
不等,props
相等。第一種:state
和props
都相等
不論是否訂閱ownProps
,執行mapStateToProps
, 由於state
有變更。
只有訂閱了ownProps
,纔會執行mapDispatchToProps
,由於state
變更與mapDispatchToProps
無影響。
一定執行,將全部結果合併。
第二種:state
相等,props
不等
只有訂閱了ownProps
,纔會執行mapStateToProps
, 由於state
無變更。
只有訂閱了ownProps
,纔會執行mapDispatchToProps
,由於state
變更與mapDispatchToProps
無影響。
一定執行,將全部結果合併。
第三種:state
不等,props
相等
不論是否訂閱ownProps
,執行mapStateToProps
, 由於state
有變更。
注意,這裏結果須要淺比較
判斷
由於若是沒有淺比較
檢查,而二者恰好淺比較相等
,
那麼最後也會認爲返回一個新的props,也就是至關於重複更新了。
之因此第一個state
和props
都有變更的不須要淺比較檢查,
是由於若是props
變了,則必需要更新組件。
不會執行,由於它只關注props
。
只有上面淺比較不等,纔會執行。
makeDerivedPropsSelector
的總結:
經過閉包管理數據,而且經過淺比較和全等比較判斷是否須要更新組件數據。
makeChildElementSelector
也是一個高階函數,儲存了以前的數據
和組件
,而且判斷與當前的判斷。
這裏是最終渲染組件的地方,由於須要判斷一下剛纔最終給出的數據是否須要去更新組件。
2個邏輯:
===
),更新組件。forWardRef
屬性值與以前不等,更新組件。不然,返回舊組件(不更新)。
繼續回到Connect
組件。
以後就是render
了
render() { // React的createContext const ContextToUse = this.props.context || Context return ( <ContextToUse.Consumer> {this.renderWrappedComponent} </ContextToUse.Consumer> ) }
Context.Consumer
內部必須是一個函數,這個函數的參數就是Context.Provider
的value
,也就是redux
的store
。
最後一個函數:renderWrappedComponent
renderWrappedComponent(value) { /* ...驗證參數有效性... */ // 這裏 storeState=store.getState() const { storeState, store } = value // 傳入自定義組件的props let wrapperProps = this.props let forwardedRef if (forwardRef) { wrapperProps = this.props.wrapperProps forwardedRef = this.props.forwardedRef } // 上面已經講了,返回最終數據 let derivedProps = this.selectDerivedProps( storeState, wrapperProps, store ) // 返回最終渲染的自定義組件 return this.selectChildElement(derivedProps, forwardedRef) }
總算結束了,可能有點混亂,作個總結吧。
我把react-redux
的執行流程分爲3個階段,分別對應咱們的代碼編寫(搭配導圖閱讀)
一張導圖:
第一階段:
對應的用戶代碼:
<Provider store={store}> <App /> </Provider>
執行內容有:
Provider
組件,這個組件內部訂閱了redux
的store
,保證當store
發生變更,會馬上執行更新。第二階段:
對應的用戶代碼:
connect(mapStateToProps,mapDispatchToProps,mergeProps,options)
執行內容有:
connect
接收了參數(mapStateToProps
,mapDispatchToProps
,mergeProps
,options
)。connect
接收了對參數如何處理方案(3個...Factories
)。connect
接收了參數的結果比較方案(selectFactory
)are...Equal
,其實就是全等比較
和淺比較
)。第三階段:
對應的用戶代碼:
let newComponent=connect(...)(Component) <newComponent />
執行內容有:
Component
)。Connect
組件。Component
的非React
靜態方法轉移到Connect
。Provider
傳入的數據
(redux
的整個數據),利用閉包保存數據,用於和將來數據作比較。===
)有變更,執行上一階段傳入的參數,獲取當前組件真正的數據。state
變更和props
變更的邏輯,判斷返回新數據仍是舊數據。邏輯理順了,仍是很好理解的。
其中第三階段就是對外APIconnectAdvanced
的執行內容。
此處查看更多前端源碼閱讀內容。
或許哪一天,咱們須要設計一個專用的數據管理系統,那麼就利用好connectAdvanced
,
咱們要作的就是編寫一個自定義第二階段
的邏輯體系。
感謝閱讀!