react-redux 版本號 7.2.3
react-redux 依賴的庫:html
"dependencies": { "@babel/runtime": "^7.12.1", "@types/react-redux": "^7.1.16", "hoist-non-react-statics": "^3.3.2", "loose-envify": "^1.4.0", "prop-types": "^15.7.2", "react-is": "^16.13.1" }
這裏我直接把 react-redux 的源碼下載了下來,因此這些依賴就必須手動安裝了react
注意: 關於 hooks 的解析會放到下一文git
redux 是一個庫,但更是一種思想, 而 react-redux 就是一座橋了, 他鏈接了兩中模式, 如今讓咱們一探究竟github
咱們將 redux 使用的流程分紅 3 個模塊redux
想要理解此中源碼首先就須要理解不少 react hooks 的知識點 還有熟練使用 redux 的經驗, 這裏我就先簡介一下設計模式
咱們要先理解一個設計模式 - 訂閱發佈模式 他位於文件: react-redux/src/utils/Subscription.js 具體的代碼咱們會在後面細說api
關於 hooks 中 咱們須要瞭解到的知識點:緩存
具體的知識點還須要去官網瞭解: https://zh-hans.reactjs.org/d...babel
關於 store 的建立併發
store 使用的主要就是 redux 的 api, 無論 combineReducers
仍是 createStore
關於 redux 的 store 提供瞭如下 API:
export interface Store<S = any, A extends Action = AnyAction> { // dispatch 的動做 dispatch: Dispatch<A> // 返回應用程序的當前狀態樹。 getState(): S // 添加更改偵聽器。每當分派動做時,都會調用它,而且狀態樹的某些部分可能已更改。而後,您能夠調用`getState()`來讀取回調中的當前狀態樹。 subscribe(listener: () => void): Unsubscribe // 替換 reducer replaceReducer(nextReducer: Reducer<S, A>): void }
通常來講, 咱們會在項目入口處加上 Provider
, 如 index.js:
ReactDOM.render( <Provider store={store}> <App/> </Provider>, document.getElementById('root') );
Provider
接受一個 store
做爲存儲, 在 <App/>
中, 任意組件都能獲取到 store
中的參數和方法
此外咱們還能 提供一個 context
給他, 可是通常不建議這樣作, 若是不夠熟悉, 會出現不少未知問題
能夠查看文件: react-redux/src/components/Provider.js
//... // Provider 主體, 是一個組件, 一般在項目的入口使用 function Provider({ store, context, children }) { const contextValue = useMemo(() => { // 建立了一個訂閱模式, 值爲 store // 賦值 onStateChange 爲 notifyNestedSubs, 做用 綁定了 store, 若是 store 值發生了變化 則執行 listener 裏的所並回調 const subscription = new Subscription(store) subscription.onStateChange = subscription.notifyNestedSubs return { store, subscription, } }, [store]) // 用來獲取store 的值 記錄,做爲對比 const previousState = useMemo(() => store.getState(), [store]) // useIsomorphicLayoutEffect 等於 useLayoutEffect useIsomorphicLayoutEffect(() => { const { subscription } = contextValue // 在 provider 裏面 對於 store 添加 onStateChange 回調, 至關於 subscribe 包裹了一層函數, 這一層的做用後面會體如今 connect 中 // 除了添加回調 還初始化了 listeners subscribe 事件的機制 subscription.trySubscribe() if (previousState !== store.getState()) { // 當知青儲存的值和當前值不一致時 觸發 listeners 裏的回調 subscription.notifyNestedSubs() } return () => { // 解除事件的監聽 subscription.tryUnsubscribe() subscription.onStateChange = null } }, [contextValue, previousState]) // context, 若是外部提供了 則使用外部的 const Context = context || ReactReduxContext // 就是 context 的 provider return <Context.Provider value={contextValue}>{children}</Context.Provider> } // ...
到這裏咱們就碰到了 Subscription
了, 如今須要知道的兩點:
subscription.addNestedSub(listener)
函數, 添加監聽事件subscription.notifyNestedSubs()
, 觸發以前全部的監聽事件subscription.trySubscribe()
屬於第一點中函數的子函數, 效果出來不能添加回調之外,相似subscription.tryUnsubscribe()
, 與第三點相反, 解除監聽如今, 咱們羅列下 Provider 作了什麼事情:
真正的重頭戲來了, 將 redux 的 store 與任意的組件鏈接
在這裏咱們首先須要知道的是 connect
, 經過他是怎麼使用的, 倒推回去看源碼會更有幫助 他的定義:
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
能夠看到connect
可接受 4 個參數
mapStateToProps?: (state, ownProps?) => Object
他是一個函數, 接受 state 和 ownProps 兩個參數, 返回一個對象, 若是 mapStateToProps 傳遞的是一個函數, 那麼 store 更新的時候,包裝的組件也會訂閱更新 若是傳遞 undefined 或者
null, 能夠避免不須要的更新
關於 ownProps
的用法, ownProps 其實就是組件的 props
const mapStateToProps = (state, ownProps) => ({ todo: state.todos[ownProps.id], })
mapDispatchToProps?: Object | (dispatch, ownProps?) => Object
第二個參數, 能夠是函數, 能夠是對象, 也能夠是空值 若是是函數, 則能夠收取到兩個參數, dispatch
和 ownProps
一般咱們是這樣作的:
const mapDispatchToProps = (dispatch) => { return { increment: () => dispatch({ type: 'INCREMENT' }), decrement: () => dispatch({ type: 'DECREMENT' }), } }
ownProps 的用法和 mapStateToProps 相同 當前參數若是是一個對象的時候,
須要控制裏面的屬性都是 action-creator
在源碼中將會調用: bindActionCreators(mapDispatchToProps, dispatch)
來生成可用代碼
官網中的簡介: 點擊查看
mergeProps?: (stateProps, dispatchProps, ownProps) => Object
這個參數的做用就是, 當前 connect 包裝的組件, 對於他的 props 再次自定義 ,如不傳遞這個屬性, 則代碼中默認傳遞值爲: { ...ownProps, ...stateProps, ...dispatchProps }
options?: Object
Object 中的內容:
{ context?: Object, pure?: boolean, areStatesEqual?: Function, areOwnPropsEqual?: Function, areStatePropsEqual?: Function, areMergedPropsEqual?: Function, forwardRef?: boolean, }
只有版本再 >=6.0 的時候纔會有這個屬性, 都是配置性的屬性, 通常來講默認值就能應付 99% 的狀況了
更加具體的做用能夠在此處點擊查看: 點擊查看
這是一個普通的用法:
connect(mapStateToProps, mapDispatchToProps)(App);
不難理解, connect 做爲一個高階函數, 返回的也是一個函數, 因此纔會是這種用法
const connect = (mapStateToProps, mapDispatchToProps)=>{ return (Component) => { return <Conponent /> } }
具體應該就是這樣, 如今帶着咱們的理解和疑問再來進入 connect 源碼
查看 connect 的入口文件 src/connect/connect
:
這個文件定義了一個 createConnect
函數, 這是用來生成 connect 的:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory, } = {}) { // 返回真正的 connect 函數 return function connect( mapStateToProps, mapDispatchToProps, mergeProps, { pure = true, areStatesEqual = strictEqual, areOwnPropsEqual = shallowEqual, areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions } = {} ) { // 判斷 mapStateToProps 是否符合已經定義的規則 // mapStateToPropsFactories 能夠想象成你對 // mapStateToProps 作了一些判斷, 只要有一個判斷符合了 // 就能夠成功返回值 // mapStateToPropsFactories 的規則會在 react-redux/src/connect/mapStateToProps.js 裏講解 // 默認的 defaultMapStateToPropsFactories 有兩個規則 // 1. 若是是函數, 會使用 wrapMapToPropsFunc 包裹, 而且直接return結果 // 2. 若是沒有傳值, 則會使用 wrapMapToPropsConstant 包裹 const initMapStateToProps = match( mapStateToProps, mapStateToPropsFactories, 'mapStateToProps' ) // 同上 可是 他的默認規則是 defaultMapDispatchToPropsFactories // 在 react-redux/src/connect/mapDispatchToProps.js 此文件中 const initMapDispatchToProps = match( mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps' ) // 同上 const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') // 包裹組件的高階函數 connect(mapStateToProps, ...) return connectHOC(selectorFactory, { // 方便 error messages 打印 methodName: 'connect', // 用於從包裝的組件的displayName計算Connect的displayName。 getDisplayName: (name) => `Connect(${name})`, // 若是mapStateToProps 爲 falsy,則Connect組件不訂閱存儲狀態更改 shouldHandleStateChanges: Boolean(mapStateToProps), // 傳遞給 selectorFactory 的參數 initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // ...extraOptions, }) } }
defaultMapStateToPropsFactories
, defaultMapDispatchToPropsFactories
, defaultMergePropsFactories
, defaultSelectorFactory
咱們會放在下面研究, 如今先知道他是作什麼的
一樣的, 在這個文件 咱們能夠看到 connect 的雛形了
真實的執行順序: createConnect()
-> connect()
-> connectHOC()
= connectAdvanced()
-> wrapWithConnect(Component)
下一步就是 connectAdvanced()
中執行了什麼:
這個咱們須要在 react-redux/src/components/connectAdvanced.js
這個文件中查看:
connectAdvanced
較爲複雜, 咱們將它分段提取, 首先咱們來看他的傳參
export default function connectAdvanced( // 這些是 connect 第一步中提供的參數 selectorFactory, // 默認爲 defaultSelectorFactory // options object: { //用於從包裝的組件的displayName計算此HOC的displayName的函數。 getDisplayName = (name) => `ConnectAdvanced(${name})`, // 在 error message 中顯示 容易 debug methodName = 'connectAdvanced', // 沒有太大做用, 後面可能會刪除 renderCountProp = undefined, // 肯定這個 hoc 是否會監聽 store 的改變 shouldHandleStateChanges = true, // 沒有太大做用, 後面可能會刪除 storeKey = 'store', // 沒有太大做用, 後面可能會刪除 withRef = false, // 是否使用了 forwardRef forwardRef = false, // 使用的 Context context = ReactReduxContext, //額外參數 ...connectOptions } = {} ) { // 省略了參數的校驗 const Context = context return function wrapWithConnect(WrappedComponent) { // ... } }
這些參數都是能夠在 createConnect
中找到, 能夠看到 connectAdvanced
返回的 wrapWithConnect
, 就是咱們用來正真返回的, 用來包裹組件的函數
在 wrapWithConnect
也有一個主體函數 ConnectFunction
, 這裏咱們先講除此函數以外的做用
function wrapWithConnect(WrappedComponent) { // 省略校檢 const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component' const displayName = getDisplayName(wrappedComponentName) // 上面兩行都是獲取組件名稱 默認(Component) const selectorFactoryOptions = { ...connectOptions, getDisplayName, methodName, renderCountProp, shouldHandleStateChanges, storeKey, displayName, wrappedComponentName, WrappedComponent, } // 將 WrappedComponent 和 connectAdvanced中的參數集合在了一塊兒 const {pure} = connectOptions // 第一步傳遞過來的參數 默認爲 true // 建立子選擇器的函數 聲明 function createChildSelector(store) { return selectorFactory(store.dispatch, selectorFactoryOptions) } // 若是 pure 爲 false, 則直接指向回調 而不是 useMemo const usePureOnlyMemo = pure ? useMemo : (callback) => callback() // 當前整個函數的主體部分 接受 props 返回 JSX 而且會用 Context 包裹 function ConnectFunction(props) { // 省略函數主體 } // 經過 pure 來肯定是否要加 memo const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName // forwardRef 省略 return hoistStatics(Connect, WrappedComponent) }
這裏要說下 hoistStatics
, 他來自於 hoist-non-react-statics
這個庫
簡單的來講能夠當作 Object.assign
, 可是他是組件級別的
ConnectFunction
能夠說是通過上一步的包裝以後 真正在執行中的函數
function ConnectFunction(props) { const [ propsContext, reactReduxForwardedRef, wrapperProps, ] = useMemo(() => { // 區分傳遞給包裝器組件的實際「數據」屬性和控制行爲所需的值(轉發的引用,備用上下文實例)。 // 要維護wrapperProps對象引用,緩存此解構。 // 此處使用的是官方註釋 const {reactReduxForwardedRef, ...wrapperProps} = props return [props.context, reactReduxForwardedRef, wrapperProps] }, [props]) const ContextToUse = useMemo(() => { // 用戶能夠選擇傳入自定義上下文實例來代替咱們的ReactReduxContext使用。 // 記住肯定應該使用哪一個上下文實例的檢查。 // 此處使用的是官方註釋 return propsContext && propsContext.Consumer && isContextConsumer(<propsContext.Consumer/>) ? propsContext : Context }, [propsContext, Context]) // useContext 不用多說 const contextValue = useContext(ContextToUse) // 到此處位置都是 context 的預備工做 // store 必須存在於 props 或 context // 咱們將首先檢查它是否看起來像 Redux store。 // 這使咱們能夠經過一個 「store」 props,該 props 只是一個簡單的值。 const didStoreComeFromProps = Boolean(props.store) && Boolean(props.store.getState) && Boolean(props.store.dispatch) // 確認 store 是否來自於本地 context const didStoreComeFromContext = Boolean(contextValue) && Boolean(contextValue.store) //省略報錯判斷 // 獲取 store 賦值 const store = didStoreComeFromProps ? props.store : contextValue.store // 到這是 store 的判斷 const childPropsSelector = useMemo(() => { // 子道具選擇器須要store參考做爲輸入。每當store更改時,則從新建立此選擇器。 return createChildSelector(store) }, [store]) const [subscription, notifyNestedSubs] = useMemo(() => { // 肯定這個 hoc 是否會監聽 store 的改變 默認爲 true if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY // [null, null] // new 一個新的 Subscription // 此訂閱的來源應與存儲來自何處相匹配:store vs context。 // 經過 props 鏈接到 store 的組件不該使用 context 訂閱,反之亦然。 // 文件來源 react-redux/src/utils/Subscription.js const subscription = new Subscription( store, didStoreComeFromProps ? null : contextValue.subscription ) // `notifyNestedSubs`是重複的,以處理組件在通知循環中間被取消訂閱的狀況, // 此時`subscription`將爲空。 若是修改Subscription的監聽器邏輯, // 不在通知循環中間調用已取消訂閱的監聽器,就能夠避免這種狀況。 // 此處使用的是官方註釋 const notifyNestedSubs = subscription.notifyNestedSubs.bind( subscription ) return [subscription, notifyNestedSubs] }, [store, didStoreComeFromProps, contextValue]) // 若是須要的話,肯定應該把什麼{store,subscription}值放到嵌套的context中 // ,並將該值備忘,以免沒必要要的上下文更新。 const overriddenContextValue = useMemo(() => { if (didStoreComeFromProps) { // 這個組件是直接從props訂閱一個存儲. // 咱們不但願子孫從這個存儲中讀取--不管現有的上下文值是來自最近的鏈接祖先的什麼, // 都會傳下來。 return contextValue } // 不然,把這個組件的訂閱實例放到上下文中,這樣鏈接的子孫就不會更新,直到這個組件完成以後。 return { ...contextValue, subscription, } }, [didStoreComeFromProps, contextValue, subscription]) // 每當 Redux store 更新致使計算出的子組件 props 發生變化時,咱們須要強制這個包裝組件從新渲染(或者咱們在mapState中發現了一個錯誤)。 const [ [previousStateUpdateResult], forceComponentUpdateDispatch, ] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates) // 拋出任何 mapState/mapDispatch 錯誤。 if (previousStateUpdateResult && previousStateUpdateResult.error) { throw previousStateUpdateResult.error } // 設置 ref,以協調訂閱效果和渲染邏輯之間的數值。 // 參考 經過 ref 能夠獲取,存儲值 const lastChildProps = useRef() const lastWrapperProps = useRef(wrapperProps) const childPropsFromStoreUpdate = useRef() const renderIsScheduled = useRef(false) const actualChildProps = usePureOnlyMemo(() => { // 這裏的邏輯很複雜: // 這個渲染多是由 Redux store 更新所觸發,產生了新的子 props。 // 不過,在那以後,咱們可能會獲得新的包裝 props。 // 若是咱們有新的子 props ,和相同的包裝 props , 咱們知道咱們應該按原樣使用新的子 props . // 可是,若是咱們有新的包裝props,這些可能會改變子 props ,因此咱們必須從新計算這些. // 因此,只有當包裝 props 和上次同樣時,咱們纔會使用 store 更新的子 props。 if ( childPropsFromStoreUpdate.current && wrapperProps === lastWrapperProps.current ) { return childPropsFromStoreUpdate.current } // 這極可能會致使在併發模式下發生壞事(TM)。 // 請注意,咱們之因此這樣作是由於在由存儲更新引發的渲染中, // 咱們須要最新的存儲狀態來肯定子 props 應該是什麼。 return childPropsSelector(store.getState(), wrapperProps) }, [store, previousStateUpdateResult, wrapperProps]) // 咱們須要在每次從新渲染時同步執行。 // 然而,React會對SSR中的useLayoutEffect發出警告, 避免警告 // 至關於在 useLayoutEffect 中執行, 包裝了一下 // 第一個參數是待執行函數, 第二個是函數參數, 第三個依賴 useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [ lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs, ]) // 咱們的從新訂閱邏輯只有在 store或者訂閱設置發生變化時纔會運行。 useIsomorphicLayoutEffectWithArgs( subscribeUpdates, [ shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch, ], [store, subscription, childPropsSelector] ) // 如今全部這些都完成了,咱們終於能夠嘗試實際渲染子組件了。 // 咱們將渲染後的子組件的元素進行記憶,做爲一種優化。 const renderedWrappedComponent = useMemo( () => ( <WrappedComponent {...actualChildProps} ref={reactReduxForwardedRef} /> ), [reactReduxForwardedRef, WrappedComponent, actualChildProps] ) // 若是React看到了與上次徹底相同的元素引用,它就會退出從新渲染該子元素,就像在React.memo()中被包裹或從shouldComponentUpdate中返回false同樣。 const renderedChild = useMemo(() => { // 肯定這個 hoc 是否會監聽 store 的改變, 默認是 true if (shouldHandleStateChanges) { // 若是這個組件訂閱了存儲更新,咱們須要將它本身的訂閱實例傳遞給咱們的子孫。 // 這意味着渲染相同的Context實例,並將不一樣的值放入context中。 return ( <ContextToUse.Provider value={overriddenContextValue}> {renderedWrappedComponent} </ContextToUse.Provider> ) } return renderedWrappedComponent }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]) return renderedChild }
這一部分即是 connect 的核心代碼
再肢解一下上面的代碼可分爲一下幾個步驟:
肯定 Context -> 肯定 store 來源 -> 將一個訂閱,發佈合併到 contextValue 中 -> 組件更新後, 檢查 store 值是否變化 -> 返回包裝組件
再解釋這部分代碼中的引用的部分函數: captureWrapperProps
, subscribeUpdates
代碼是在這裏:
useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [ lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs, ])
轉換一下:
useLayoutEffect(() => { captureWrapperProps( lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs ) }) function captureWrapperProps( lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs ) { lastWrapperProps.current = wrapperProps lastChildProps.current = actualChildProps renderIsScheduled.current = false // 若是渲染是來自store的更新,則清除該引用 而且觸發訂閱 if (childPropsFromStoreUpdate.current) { childPropsFromStoreUpdate.current = null notifyNestedSubs() } }
源碼:
useIsomorphicLayoutEffectWithArgs( subscribeUpdates, [ shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch, ], [store, subscription, childPropsSelector] )
一樣地通過轉換:
useLayoutEffect(() => { subscribeUpdates( shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch, ) }, [store, subscription, childPropsSelector])
咱們再看 subscribeUpdates
作了什麼, 這裏就比較複雜了:
function subscribeUpdates( shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch ) { // 若是不想從 store 中更新, 則直接返回 if (!shouldHandleStateChanges) return let didUnsubscribe = false let lastThrownError = null // 每次 store 的訂閱更新傳播到這個組件時,咱們都會運行這個回調。 const checkForUpdates = () => { if (didUnsubscribe) { // Redux不能保證取消訂閱會在下一次發送以前發生。 return } const latestStoreState = store.getState() let newChildProps, error try { // 用最新的store狀態和包裝運行選擇器 newChildProps = childPropsSelector( latestStoreState, lastWrapperProps.current ) } catch (e) { error = e lastThrownError = e } if (!error) { lastThrownError = null } // 若是沒變化就不作什麼 if (newChildProps === lastChildProps.current) { if (!renderIsScheduled.current) { notifyNestedSubs() } } else { // 保存對新的子props的引用。 lastChildProps.current = newChildProps childPropsFromStoreUpdate.current = newChildProps renderIsScheduled.current = true // If the child props _did_ change (or we caught an error), this wrapper component needs to re-render // 若是 子 props 確實發生了變化, 那麼 wrapperComponent 須要重渲染 forceComponentUpdateDispatch({ type: 'STORE_UPDATED', payload: { error, }, }) } } subscription.onStateChange = checkForUpdates subscription.trySubscribe() // 執行 checkForUpdates() // 在第一次渲染後從store拉出數據,以防store在咱們開始後發生變化。 const unsubscribeWrapper = () => { didUnsubscribe = true subscription.tryUnsubscribe() subscription.onStateChange = null // 若是出錯, 可是到此聲明週期還沒解決, 就觸發報錯 if (lastThrownError) { throw lastThrownError } } return unsubscribeWrapper }
從這幾行能夠看出來, store 或者 props 的變化都會致使此包裝組件的再渲染, 選渲染中又加上了判斷, 能夠控制子組件是否真的可以渲染
這函數是獲取 store 的, 以前使用的地方
// childPropsSelector使用 1 newChildProps = childPropsSelector( latestStoreState, lastWrapperProps.current ) // childPropsSelector使用 2 childPropsSelector(store.getState(), wrapperProps) const childPropsSelector = useMemo(() => { return createChildSelector(store) }, [store]) function createChildSelector(store) { return selectorFactory(store.dispatch, selectorFactoryOptions) }
在默認狀況下 selectorFactory = defaultSelectorFactory
源文件: react-redux/src/connect/selectorFactory.js
defaultSelectorFactory
別名: finalPropsSelectorFactory
// 若是pure爲true,則selectorFactory返回的選擇器將記住其結果, // 若是未更改結果,則connectAdvanced的shouldComponentUpdate能夠返回false。 // 若是爲false,則選擇器將始終返回新對象,而shouldComponentUpdate將始終返回true。 // 默認的選擇器工廠 export default function finalPropsSelectorFactory( dispatch, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options } ) { // initMapStateToProps 可在 connect 中查看 就是經過 match 獲取的結果 const mapStateToProps = initMapStateToProps(dispatch, options) const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options) // 忽略驗證 const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options ) }
這裏再次進行到下一流程 initMapStateToProps
, initMapDispatchToProps
, initMergeProps
:
這三個變量的來源是在這裏:
const initMapStateToProps = match( mapStateToProps, mapStateToPropsFactories, 'mapStateToProps' ) const initMapDispatchToProps = match( mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps' ) const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
而這, 咱們又接觸到了新的幾個參數 match
, mapStateToPropsFactories
,mapDispatchToPropsFactories
,mergePropsFactories
:
先說說 match, 來看看源碼:
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
: defaultMapStateToPropsFactories
, defaultMapDispatchToPropsFactories
, defaultMergePropsFactories
export function whenMapStateToPropsIsFunction(mapStateToProps) { return typeof mapStateToProps === 'function' ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') : undefined } export function whenMapStateToPropsIsMissing(mapStateToProps) { return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined } export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
通過以前咱們的 connect
用法的介紹:
mapStateToProps?: (state, ownProps?) => Object
若是 mapStateToProps
傳的是一個函數, 則用 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) if (typeof props === 'function') { proxy.mapToProps = props proxy.dependsOnOwnProps = getDependsOnOwnProps(props) props = proxy(stateOrDispatch, ownProps) } // 註釋驗證 return props } return proxy } } // dependsOnOwnProps默認爲 true // 判斷 mapToProps 的dependsOnOwnProps屬性是否爲空, // 若是不爲空則, 則返回 Boolean(dependsOnOwnProps), 若是爲空, 則比較後返回 布爾值 function getDependsOnOwnProps(mapToProps) { return mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length !== 1 }
經歷過 match 的遍歷, 返回的就是 initProxySelector
, 這個地方設計得很巧妙initProxySelector
的時候, 傳入值: dispatch, options
, 這裏 options 能夠暫時忽略 , 這裏是有mapToProps的入參
他的返回結果也是一個函數, 即 proxy 函數
執行的是 proxy.mapToProps(stateOrDispatch, ownProps)
即 detectFactoryAndVerify
覆蓋原 mapToProps
: proxy.mapToProps = mapToProps
這裏覆蓋的就是咱們傳入的 mapStateToProps
函數 / 或者 undefined, proxy.dependsOnOwnProps
正常狀況下都是返回 true
這時候 再次執行 proxy
: let props = proxy(stateOrDispatch, ownProps)
轉換一下: mapToProps(stateOrDispatch, ownProps)
, 這裏的 mapToProps
是咱們傳入的,
以後繼續往下走:
if (typeof props === 'function') { proxy.mapToProps = props proxy.dependsOnOwnProps = getDependsOnOwnProps(props) props = proxy(stateOrDispatch, ownProps) }
這裏是對於返回結果又作了一層判斷, 若是返回的是一個函數, 將會覆蓋
由於這個判斷, 因此咱們能夠這樣傳 mapStateToProps
:
const mapStateToProps = (_state) => { return (state) => ({ value: state.value }) }
因此說查看源碼, 是能夠發現一些新的用法的, 雖然這樣的寫法不是很提倡, 暫時沒什麼做用, 但爲後面他的拓展性作了很充足的準備
這個參數是和 defaultMapStateToPropsFactories
相似了
並且由於 mapDispatchToProps
能夠傳入 Object
, 在 match
上又會多一層判斷
這裏涉及到的代碼和 defaultMapStateToPropsFactories
90% 都是相似了, 因此跳過
同上, 基本相似, 也再也不贅述
到這裏繼續講 selectorFactory
, 通常來講 selectorFactory
運行的都是此函數 pureFinalPropsSelectorFactory
,
代碼一樣是在 react-redux/src/connect/selectorFactory.js
此文件夾下的
入參:
mapStateToProps, mapDispatchToProps, mergeProps, dispatch, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
在此函數體內, 他又定義了幾個函數, 最關鍵是這個:
function handleSubsequentCalls(nextState, nextOwnProps) { const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) const stateChanged = !areStatesEqual(nextState, state) state = nextState ownProps = nextOwnProps if (propsChanged && stateChanged) return handleNewPropsAndNewState() if (propsChanged) return handleNewProps() if (stateChanged) return handleNewState() return mergedProps } return function pureFinalPropsSelector(nextState, nextOwnProps) { // 表示是否已經運行過一次, 若是沒有,則使用 handleFirstCall 初始化 // 不然使用 handleSubsequentCalls return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps) }
這裏的比較方法areOwnPropsEqual
,areStatesEqual
默認都是 shallowEqual
(來自文件: react-redux/src/utils/shallowEqual.js), 算是一個淺層比較
經過比較 新舊 props 和 state, 若是發生變化, 則進行對應的更新最終返回合併後的值
咱們傳遞的 mapStateToProps
->
通過 match 函數
-> match
函數中的 wrapMapToPropsFunc
->
如今執行的是 initProxySelector
->
別名 initMapStateToProps
->
經過執行他 得到結果 const mapStateToProps = initMapStateToProps(dispatch, options)
->
經過 finalPropsSelectorFactory
的包裝 ->
別名 selectorFactory
->
在函數中杯執行 selectorFactory(store.dispatch, selectorFactoryOptions)
->
返回的值, 做爲 childPropsSelector
的值 ->
在新舊 props 比較時(subscribeUpdates中)使對此這個值
本文較爲簡單的解析了一下 react-redux
這個經常使用庫, 總結了幾種代碼運行流程
createConnect()
-> connect()
-> connectHOC()
= connectAdvanced()
-> wrapWithConnect(Component)
mapStateToProps
-> match 函數
-> match
函數中的 wrapMapToPropsFunc
-> initProxySelector
-> initMapStateToProps
-> const mapStateToProps = initMapStateToProps(dispatch, options)
-> finalPropsSelectorFactory
的包裝 -> selectorFactory
-> selectorFactory(store.dispatch, selectorFactoryOptions)
-> childPropsSelector
的值 -> 本文記錄:
https://github.com/Grewer/rea...
參考文檔:
https://react-redux.js.org/in...
https://github.com/reduxjs/re...