React-redux源碼總共分爲兩部分,第一是Provider
,第二是connect
。react
Provider比較簡單,主要代碼以下:git
class Provider extends Component { getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } } constructor(props, context) { super(props, context) this[storeKey] = props.store; } render() { return Children.only(this.props.children) } } 複製代碼
即經過context api將store傳到子組件裏面去。github
connect
算是一個比較有難度的部分,函數原型是:redux
connect(mapStateToProps,mapDispatchToProps,[mergeProps]) => (WrappedComponent) => HOC 複製代碼
咱們嘗試對其進行由上到下的分析:api
函數入口就是一個三階函數:數組
export function createConnect({ connectHOC, mapStateToPropsFactories, mapDispatchTpropsFactories, mergePropsFactories, selectFactory }) => connect(mapStateToProps,mapDispatchToProps,mergeProps, {/*...*/}) => { const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps') const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') return connectHOC(selectorFactory, { initMapStateToProps, initMapDispatchToProps, initMergeProps, //... }) } export default createConnect() 複製代碼
寫的其實比較繞,可是理解起來不難,首先createConnect
方法只至關於一個包裝而已,將高階組件connectHOC,以及一些工廠函數引入到connect
的父scope中來。markdown
而後在connect
中,經過match函數來選擇適當的factory來包裝外界傳入的參數)生成三個函數: initMapStateToProps
,initMapDispatchToProps
和initMergeProps
。閉包
match函數的源碼:app
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}.`) } } 複製代碼
這段代碼其中arg
就是connect函數接收的mapStateToProps
等,以倒序的方式尋找第一個返回非空的factory。咱們能夠先暫且不去深刻推敲。ide
connectHOC
是整個react-redux的核心,在分析源代碼以前,咱們先看看一個工具函數:
function makeSelectorStateful(sourceSelector, store) { 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 } 複製代碼
selector主要做用是對redux的state和dispatch與react的props進行合併(mergeProps
) 這段代碼主要是對sourceSelector
進行封裝,增長了錯誤處理,新舊值的比較等。
下面是connectHOC
的主要源碼:
// ... export default function connectAdvanced( selectorFactory, // options object: { //省略掉的參數: withRef,displayName,renderCountProp,shouldHandleStateChanges,storeKey,getDisplayName // 傳入selectorFactory中額外的參數 // 主要包括 `initMapStateToProps`,`initMapDispatchToProps`和`initMergeProps` ...connectOptions } = {} ) { const subscriptionKey = storeKey + 'Subscription' // ... return function wrapWithConnect(WrappedComponent) { // ... const selectorFactoryOptions = { ...connectOptions, //... WrappedComponent } // 聲明一個React組件 class Connect extends Component { constructor(props, context) { super(props, context) // ... // 從props或者context裏面得到redux store this.store = props[storeKey] || context[storeKey] this.propsMode = Boolean(props[storeKey]) this.initSelector() this.initSubscription() } initSelector() { // 結合mapPropsTo...和mergeProps函數處理參數。(state, props) => nextProps const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions) // makeSelectorStateful: 包裝sourceSelector函數,比較props和nextProps肯定shouldComponentUpdate this.selector = makeSelectorStateful(sourceSelector, this.store) // 更新當前參數,實際上就是ownProps this.selector.run(this.props) } initSubscription() { // 若是不須要相應redux state變化,則不須要訂閱 if (!shouldHandleStateChanges) return // 獲取祖先傳下來的訂閱對象 const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey] // 生成當前組件的訂閱對象,傳入parentSub。 // 當parentSub不爲空的時候,onStateChange其實是註冊到了parentSub的listeners數組,由組件手動調用notifyNestedSubs執行。 // 不然直接使用store.subscribe註冊到redux中,由redux控制執行 // 這樣作能始終保證祖先節點的更新早於子孫節點 this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this)) this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription) } onStateChange() { // 更新props this.selector.run(this.props) if (!this.selector.shouldComponentUpdate) { // 若是當前組件不須要更新,那麼直接更新子組件 this.notifyNestedSubs() } else { // 先更新當前組件,再更新子組件 this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate // dummyState 爲空,意味觸發react的更新機制 this.setState(dummyState) } } getChildContext() { // 若是store是從props中得到,那麼直接將祖先的subscription對象傳下去。(僅做爲一個傳話人,不響應父組件的dispatch事件) // 不然,將自身的subscription傳下去,形狀以下 // Component A (B's subscription's parentSub == A) // | // -> Component B (C's subscription's parentSub == B) // | // -> Component C (E's subscription's parentSub == C) // | // |== Component D (propsMode === true) directly transfer C's sub context to E // | // -> Component E // when a dispatch occured in Component A,the callback exec order will be A -> B -> C -> E const subscription = this.propsMode ? null : this.subscription return { [subscriptionKey]: subscription || this.context[subscriptionKey] } } componentDidMount() { if (!shouldHandleStateChanges) return // 由於componentWillMount在ssr中執行,而componentDidMount和componentWillUnmount不執行。所以若是將trySubscribe方法放在componentWillMount中的話,在ssr中可能會由於沒有調用componentWillUnmount的unsubscribe而內存泄漏 this.subscription.trySubscribe() // run即update,更新selector.nextProps,selector.shouldComponentUpdate this.selector.run(this.props) // 若是子組件在componentWillMount的時候dispatch了一個action,於是沒有被上文的subscription捕獲到,此時的selector就會檢測出參數的不一致(shouldComponentUpdate = true),因此須要強制更新 if (this.selector.shouldComponentUpdate) this.forceUpdate() } // 更新props componentWillReceiveProps(nextProps) { this.selector.run(nextProps) } shouldComponentUpdate() { return this.selector.shouldComponentUpdate } componentWillUnmount() { if (this.subscription) this.subscription.tryUnsubscribe() // 爲何直接使用subscription.notifyNestedSubs而要拷貝出來分別處理: // 防止在notify過程當中subscription爲null,而null.notifyNestedSubs報錯 this.subscription = null this.notifyNestedSubs = noop this.store = null this.selector.run = noop this.selector.shouldComponentUpdate = false } notifyNestedSubsOnComponentDidUpdate() { // 避免屢次調用 this.componentDidUpdate = undefined this.notifyNestedSubs() } isSubscribed() { return Boolean(this.subscription) && this.subscription.isSubscribed() } addExtraProps(props) { // 若是沒有額外的參數,直接返回props if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props // 防護性拷貝 const withExtras = { ...props } if (withRef) withExtras.ref = this.setWrappedInstance if (renderCountProp) withExtras[renderCountProp] = this.renderCount++ if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription return withExtras } render() { const selector = this.selector selector.shouldComponentUpdate = false if (selector.error) { throw selector.error } else { return createElement(WrappedComponent, this.addExtraProps(selector.props)) } } } // ... // 僅在開發環境下支持的的熱重載 if (process.env.NODE_ENV !== 'production') { Connect.prototype.componentWillUpdate = function componentWillUpdate() { // We are hot reloading! if (this.version !== version) { this.version = version this.initSelector() let oldListeners = []; if (this.subscription) { oldListeners = this.subscription.listeners.get() this.subscription.tryUnsubscribe() } this.initSubscription() if (shouldHandleStateChanges) { this.subscription.trySubscribe() oldListeners.forEach(listener => this.subscription.listeners.subscribe(listener)) } } } } // 將WrappedComponent中的非React特定的靜態屬性(例如propTypes就是React的特定靜態屬性)賦值到Connect。 // 做用有點相似於Object.assign,可是僅複製非React特定的靜態屬性。 return hoistStatics(Connect, WrappedComponent) } } 複製代碼
至此,大部分邏輯咱們已經很清楚了。如今咱們來分析一下各類factory所作的事情:
首先看主體:
export default function finalPropsSelectorFactory(dispatch, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }) { 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 return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options ) } 複製代碼
該函數至關於一個控制器,根據pure
選項來決定使用那種具體實現。 若是pure
爲true
,則selectorFactory將會返回一個能夠記憶結果的selector,這樣纔有可能使得connectHOC
裏面的selector.shouldComponentUpdate
爲false
。反之,selector將永遠返回新的對象,selector.shouldComponentUpdate
始終爲true
export function impureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch ) { return function impureFinalPropsSelector(state, ownProps) { return mergeProps( mapStateToProps(state, ownProps), mapDispatchToProps(dispatch, ownProps), ownProps ) } } 複製代碼
直接就很粗暴了
export function pureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual } ) { // 閉包,memory以前的結果,僅當有變化的時候才更新 let hasRunAtLeastOnce = false let state let ownProps let stateProps let dispatchProps // 合併的結果 let mergedProps // 第一次調用 function handleFirstCall(firstState, firstOwnProps) { state = firstState ownProps = firstOwnProps stateProps = mapStateToProps(state, ownProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) hasRunAtLeastOnce = true return mergedProps } function handleNewPropsAndNewState() { // state 已經 changed stateProps = mapStateToProps(state, ownProps) if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewProps() { // 僅當state 用到了OwnProps才更新 if (mapStateToProps.dependsOnOwnProps) stateProps = mapStateToProps(state, ownProps) // 僅當disaptch 用到了OwnProps才更新 if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewState() { const nextStateProps = mapStateToProps(state, ownProps) // shallow compare state是否改變 const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps) stateProps = nextStateProps if (statePropsChanged) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } // 非首次調用 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) { return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps) } } 複製代碼
看上去複雜許多,但主要仍是由於須要區分是不是首次調用而已。
mapStateToProps
和mapDispatchToProps
差很少,大部分都是公共代碼(wrapMapToProps
),所以咱們以mapStateToProps
爲例:
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' export function whenMapStateToPropsIsFunction(mapStateToProps) { return (typeof mapStateToProps === 'function') ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') : undefined } export function whenMapStateToPropsIsMissing(mapStateToProps) { return (!mapStateToProps) ? wrapMapToPropsConstant(() => ({})) : undefined } export default [ whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing ] 複製代碼
跟selectorFactory同樣,這裏一樣有多種解決方案,不過不一樣的是,這裏是直接將全部方案以數組的方式返回,供前面connect
中的match
函數匹配。
而後咱們主要來看一下其中涉及到的幾個函數:
export function wrapMapToPropsConstant(getConstant) { return function initConstantSelector(dispatch, options) { const constant = getConstant(dispatch, options) function constantSelector() { return constant } constantSelector.dependsOnOwnProps = false return constantSelector } } 複製代碼
顧名思義,constant不容許結果發生變化的,因此函數裏面調用傳入的getConstant
函數以後,直接返回一個getter函數:contantSelector
// methodName只用來debug export function wrapMapToPropsFunc(mapToProps, methodName) { // options 只須要 displayname , 也只用來debug 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) // 若是mapToProps執行後返回的結果仍是一個函數,則繼續遞歸調用,直到它扁平爲止 if (typeof props === 'function') { proxy.mapToProps = props proxy.dependsOnOwnProps = getDependsOnOwnProps(props) props = proxy(stateOrDispatch, ownProps) } if (process.env.NODE_ENV !== 'production') verifyPlainObject(props, displayName, methodName) return props } return proxy } } 複製代碼
這裏主要運用了代理的方法,挺巧妙的對mapToProps
方法進行了包裝,增長了dependsOnOwnProps
這個靜態成員,判斷方法很簡單,直接判斷Function的參數長度便可:
export function getDependsOnOwnProps(mapToProps) { return (mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined) ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length !== 1 } 複製代碼
至此,全部代碼都通了, cheers!~