react-redux connect原始碼解讀
今天看了下react-redux
的原始碼,主要來看下connect
的方法
首先找到connect
的入口檔案。在src/index.js
下找到。對應connect
資料夾下的connect.js
檔案。
-
大致說下原始碼
connect
流程
connect.js
對外暴露是通過export default createConnect()
, 我們來看createConnect
方法。
在這裡,他是在對外暴露的時候直接執行,導致對外匯出的結果就是他返回的一個connect
方法.
然後再connect
裡他是返回了一個函式的結果。這個就對應到createHoc
所對應的connectAdvanced我們的容器元件
. -
具體細節
看下面部分的原始碼:
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
} = {}) {
return function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true,
areStatesEqual = strictEqual,
areOwnPropsEqual = shallowEqual,
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
...extraOptions
} = {}
) {
const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
return connectHOC(selectorFactory, {
// used in error messages
methodName: 'connect',
// used to compute Connect's displayName from the wrapped component's displayName.
getDisplayName: name => `Connect(${name})`,
// if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
shouldHandleStateChanges: Boolean(mapStateToProps),
// passed through to selectorFactory
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
// any extra options args can override defaults of connect or connectAdvanced
...extraOptions
})
}
}
我們知道,在容器元件平時對外暴露的時候是
const mapStateToProps = (state, ownProps) => ({
// ...
})
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(Object.assign({}, actions), dispatch)
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(YourComponent)
我們可以看到基本都是都是傳遞兩個引數,mapStateToProps&mapDispatchToProps
, 第三個引數mergeProps
並沒有傳遞,這個引數是幹什麼的?
他在
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
這裡初始化mergeProps
,通過mergePropsFactories
來處理裡返回的兩個函式來處理。
對應了兩個函式對應了mergeProps
的兩種情況:
-
whenMergePropsIsFunction
存在mergeProps
並且是function
的時候,會進行處理並返回一個函式,
這個函式的第一個引數是一個dispatch
,第二個引數是一個物件,並且物件接收三個屬性,如果不是一個方法,那就返回undefined
. -
whenMergePropsIsOmitted
方法會判斷mergeProps
是不是存在,如果存在就返回undefined
,否則就會返回一個預設的mergeProps
mapStateToProps&mapDispatchToProps
和mergeProps
的初始化類似,都會進行判斷再去操作。
然後connect
後面的第四個引數是一個物件,他接收一些屬性。
然後就是下一步了,在使用的時候,我們connect()
之後就是繼續呼叫,傳參是我們的容器元件。那這時,在原始碼裡就是對應的return connectHOC(selectorFactory, {...})
,
這個返回的是一個函式執行的結果,所以我們傳參的容器元件這個引數,並不是這裡的selectorFactory
,而是他執行完成之後的結果所接受的引數。
上面有提到createHOC
對應的是connectAdvanced
.然後可以看到createHOC
傳參的第一個是selectorFactory
,
就是對應的defaultSelectorFactory
,然後第二個引數是一些物件。
接下來我們就可以看看對應createHOC
的connectAdvanced
方法了。
首先看connectAdvanced
在引數方面,第一個保持不變,第二個進行了擴充套件,添加了幾個屬性。
我們還是從對外暴露的介面來看,他直接暴露的是connectAdvanced
方法。因為我們在createHOC
所需要的是一個結果,
所以我們通過原始碼看看他是怎麼執行的.
在這個方法裡,有返回一個方法wrapWithConnect(WrappedComponent){}
,好傢伙,到這裡我們可以知道connect
裡的return createHOC(...)
的結果返回
的就是這個,然後再看返回的這個函式名稱,很見名知意用connect包裹的函式的引數是包裹的元件
.那就看看這個函式裡面是啥。
一些基本的處理,然後就是一個connect
基於React.Component
的繼承,最後是返回return hoistStatics(Connect, WrappedComponent)
,
這個hoistStatics
是一個類似Object.assign
的copy
非react
的static property
.
換句話說,就是把傳遞的容器元件自定義的靜態屬性附加到connect
元件上去。
我們再看看這個connect class
, 他在建構函式裡直接運行了this.initSelector()和this.initSubscription()
我們先看下initSelector
,
發現就是使用的selectorFactory
方法來處理的,
我們就看看這個方法是怎麼處理的。對外暴露的方法在這裡,
他會根據pure
屬性來確定到底該如何處理。其實我們從這一段註釋就可以知道了:
// If pure is true, the selector returned by selectorFactory will memoize its results,
// allowing connectAdvanced's shouldComponentUpdate to return false if final
// props have not changed. If false, the selector will always return a new
// object and shouldComponentUpdate will always return true.
就是去處理到底是否應該重新渲染,所有如果你想要重新整理的情況,可以在connect
的傳參的第四個物件裡改變pure
為false
.
然後initSelector
同時也會為當前物件建立一個selector
:
function makeSelectorStateful(sourceSelector, store) {
// wrap the selector in an object that tracks its results between runs.
const selector = {
run: function runComponentSelector(props) {
try {
const nextProps = sourceSelector(store.getState(), props)
if (nextProps !== selector.props || selector.error) {
selector.shouldComponentUpdate = true
selector.props = nextProps
selector.error = null
}
} catch (error) {
selector.shouldComponentUpdate = true
selector.error = error
}
}
}
return selector
}
最後來run
,如果符合條件或者出錯,便會改變shouldComponentUpdate = true
,去重新 render
.
再來看看initSubscription
方法:
initSubscription() {
if (!shouldHandleStateChanges) return
const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}
先判斷shouldHandleStateChanges
是不是成立,如果成立則進行,否則返回。成立會進行初始化,正如其名,初始化訂閱相關事件。
這裡初始化結束之後,我們看下componentDidMount
方法,
componentDidMount() {
if (!shouldHandleStateChanges) return
// componentWillMount fires during server side rendering, but componentDidMount and
// componentWillUnmount do not. Because of this, trySubscribe happens during ...didMount.
// Otherwise, unsubscription would never take place during SSR, causing a memory leak.
// To handle the case where a child component may have triggered a state change by
// dispatching an action in its componentWillMount, we have to re-run the select and maybe
// re-render.
this.subscription.trySubscribe()
this.selector.run(this.props)
if (this.selector.shouldComponentUpdate) this.forceUpdate()
}
他也會和initSubscription
一樣的去判斷,確定是否進行下去。下面會進行嘗試訂閱.
然後去執行selector
,執行selector
之後,會判斷shouldComponentUpdate
,如果成立,則會進行forceUpdate
, 注意: forceUpdate
會跳過shouldComponentUpdate
的判斷.
最後看下render
方法,
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
可以看到.他會對屬性進行重寫成false
,如果報錯,就跑出錯誤,如果正常,那就進行render
建立元素到頁面。
第一次看原始碼,有些東西解釋不到位,歡迎提出於原文出處。