上一次我們講解了Provider、connect、selectorFactory。這次主要分析 connectAdvanced 這個核心API。 react-redux原始碼分析及實現原型_上
connectAdvanced
在開始之前我們先來看一個工具函式
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 會將新的值和快取的值做比較,如果變化,將重新求值並返回,如果沒變化,返回快取的舊值。makeSelectorStateful 函式是對 selector 的封裝。正如其名字一樣,使selector stateful
再介紹一下hoist-non-react-statics
這個庫,作用是避免在使用HOC時,導致類的static方法丟失的問題。詳情見react doc
Subscription
是實現react與redux繫結的類,在接下來會用到我們先來看一下
export default class Subscription {
constructor(store, parentSub, onStateChange) {
this.store = store
this.parentSub = parentSub
this.onStateChange = onStateChange
this.unsubscribe = null
this.listeners = nullListeners
}
addNestedSub(listener) {
this.trySubscribe()
return this.listeners.subscribe(listener)
}
notifyNestedSubs() {
this.listeners.notify()
}
isSubscribed() {
return Boolean(this.unsubscribe)
}
trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.onStateChange)
: this.store.subscribe(this.onStateChange)
this.listeners = createListenerCollection()
}
}
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
this.listeners.clear()
this.listeners = nullListeners
}
}
}
複製程式碼
重點在 trySubscribe 方法,如果 parentSub 存在就將回撥函式繫結在父元件上,否則繫結在store.subscribe中 原因是這樣可以保證元件的更新順序,從父到子。
然後可以開始 connectAdvanced
export default function connectAdvanced(
selectorFactory,
{
shouldHandleStateChanges = true,
storeKey = 'store',
// 傳遞給 selectorFactory 的引數
...connectOptions
} = {}
) {
return function wrapWithConnect(WrappedComponent) {
//...
const selectorFactoryOptions = {
...connectOptions,
shouldHandleStateChanges,
// ...
}
class Connect extends Component {
constructor(props, context) {
super(props, context)
this.version = version
this.state = {}
this.renderCount = 0
this.store = props[storeKey] || context[storeKey]
this.propsMode = Boolean(props[storeKey])
this.setWrappedInstance = this.setWrappedInstance.bind(this)、
// selector 與 subscription 的初始化
this.initSelector()
this.initSubscription()
}
getChildContext() {
// 如果元件從props裡獲得store,那麼將 context 中的 subscription 傳遞下去
// 否則就將傳遞此元件中的 subscription
// 子元件使用祖先元件的 subscription 可以保證元件的更新順序(父 -> 子)。
// 另外 將store通過props傳遞下去,這種場景是什麼。。。
const subscription = this.propsMode ? null : this.subscription
return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
}
componentDidMount() {
// shouldHandleStateChanges === Boolean(mapStateToProps)
// 如果沒有 mapStateToProps 元件不需要監聽store變化
if (!shouldHandleStateChanges) return
// 由於 componentWillMount 會在ssr中觸發,而 componentDidMount、componentWillUnmount不會。
// 如果將subscription放在 componentWillMount中,那麼 unsubscription 將不會被觸發,將會導致記憶體洩漏。
this.subscription.trySubscribe()
// 為了防止子元件在 componentWillMount 中呼叫dipatch 所以這裡需要在重新計算一次
// 因為子元件的 componentWillMount 先於元件的 componentDidMount 發生,此時還沒有執行 trySubscribe
this.selector.run(this.props)
if (this.selector.shouldComponentUpdate) this.forceUpdate()
}
componentWillReceiveProps(nextProps) {
this.selector.run(nextProps)
}
shouldComponentUpdate() {
return this.selector.shouldComponentUpdate
}
componentWillUnmount() {
if (this.subscription) this.subscription.tryUnsubscribe()
this.subscription = null
this.notifyNestedSubs = noop
this.store = null
this.selector.run = noop
this.selector.shouldComponentUpdate = false
}
initSelector() {
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
this.selector = makeSelectorStateful(sourceSelector, this.store)
this.selector.run(this.props)
}
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.subscription就為null了。這裡將notifyNestedSubs拷貝一次。
// 並且在componentWillUnmount 中 this.notifyNestedSubs = noop,
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}
onStateChange() {
this.selector.run(this.props)
if (!this.selector.shouldComponentUpdate) {
// 如果不需要更新則通知子元件
this.notifyNestedSubs()
} else {
// 如果需要更新則在更新之後,再通知子元件
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
// 元件更新
this.setState(dummyState)
}
}
notifyNestedSubsOnComponentDidUpdate() {
// 避免重複通知
this.componentDidUpdate = undefined
this.notifyNestedSubs()
}
isSubscribed() {
return Boolean(this.subscription) && this.subscription.isSubscribed()
}
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
}
/* eslint-enable react/no-deprecated */
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
Connect.childContextTypes = childContextTypes
Connect.contextTypes = contextTypes
Connect.propTypes = contextTypes
return hoistStatics(Connect, WrappedComponent)
}
}
複製程式碼
over~ 是不是覺得react-redux很簡單? 接下來我會將react技術棧中常用的庫(react, react-router, redux, redux-saga, dva)原始碼分析一遍,喜歡的話。可以watch me!