Provider做了什麼,發佈訂閱模式實現?
使用過react的同學都知道,redux作爲react公共狀態管理容器,配合react-redux可以很好的派發更新,更新視圖渲染的作用,那麼對於react-redux是如何做到根據state的改變,而更新組件,促使視圖渲染的呢,讓我們一起來探討一下,react-redux源碼的奧妙所在。
在正式分析之前我們不妨來想幾個問題,
1 爲什麼要在root跟組件上使用react-redux的provider組件包裹
2 redux是使用store.subscribe()來發布訂閱 ,那麼react-redux組件更新是否也是用這個模式呢
3 provide 用什麼方式存放當前的redux的 store, 又是怎麼傳遞給每一個需要管理state的組件的
帶着這些疑問我們不妨先看一下Provider究竟做了什麼
創建Subscription,context保存上下文
/* provider 組件代碼 */
function Provider({ store, context, children }) {
/* 利用useMemo,跟據store變化創建出一個contextValue 包含一個根元素訂閱器和當前store */
const contextValue = useMemo(() => {
/* 創建了一個根 Subscription 訂閱器 */
const subscription = new Subscription(store)
/* subscription 的 notifyNestedSubs 方法 ,賦值給 onStateChange方法 */
subscription.onStateChange = subscription.notifyNestedSubs
return {
store,
subscription
} /* store 改變創建新的contextValue */
}, [store])
/* 獲取更新之前的state值 ,函數組件裏面的上下文要優先於組件更新渲染 */
const previousState = useMemo(() => store.getState(), [store])
useEffect(() => {
const { subscription } = contextValue
/* 觸發trySubscribe方法執行,創建listens */
subscription.trySubscribe()
if (previousState !== store.getState()) {
/* 組件更新渲染之後,如果此時state發生改變,那麼立即觸發 subscription.notifyNestedSubs 方法 */
subscription.notifyNestedSubs() // 更新組件
}
/* */
return () => {
subscription.tryUnsubscribe() //卸載更新
subscription.onStateChange = null
}
/* contextValue state改變出發新的effect */
}, [contextValue, previousState])
const Context = context || ReactReduxContext
/* context 存在用跟元素傳進來的context ,如果不存在 createContext創建一個context ,這裏的ReactReduxContext就是由createContext創建出的context */
return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
從源碼中provider作用大致是這樣的
1 首先創建一個contextValue ,裏面包含一個創建出來的父級Subscription 我們姑且先稱之爲根級訂閱器和redux提供的store。
2 通過react上下文context把contextValue傳遞給子孫組件。
這就解釋了我們在之前的三個問題中的
1 爲什麼要用provider包裹 ,答案如上。
3 通過什麼保存store ,答案是react的context上下文。
Subscription作用是什麼呢
在我們分析了不是很長的provider源碼之後,隨之一個Subscription 出現,那麼這個Subscription由什麼作用呢,我們先來看看在Provder裏出現的Subscription方法。
notifyNestedSubs
trySubscribe
trySubscribe
tryUnsubscribe
在整個react-redux執行過程中 Subscription 作用非常重要,這裏方便先透漏一下,他的作用是收集所有被connect包裹的組件的更新函數onstatechange,然後形成一個callback鏈表,再有父級Subscription統一派發執行更新,我們暫且不關心它是怎麼運作的,接下來就是Subscription源碼 ,我們重點看一下如上出現的四個方法。
/* 發佈訂閱者模式 */
export default class Subscription {
constructor(store, parentSub) {
this.store = store
this.parentSub = parentSub
this.unsubscribe = null
this.listeners = nullListeners
this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
}
/* 負責檢測是否該組件訂閱,然後添加訂閱者也就是listener */
addNestedSub(listener) {
this.trySubscribe()
return this.listeners.subscribe(listener)
}
/* 向listeners發佈通知 */
notifyNestedSubs() {
this.listeners.notify()
}
/* 這個就是添加的訂閱着listener ,處理由redux,state而訂閱的回調函數 */
handleChangeWrapper() {
if (this.onStateChange) {
this.onStateChange()
}
}
/* 判斷有沒有開啓訂閱 */
isSubscribed() {
return Boolean(this.unsubscribe)
}
/* 開啓訂閱模式 首先判斷當前訂閱器有沒有父級訂閱器 , 如果有父級訂閱器(就是父級Subscription),把自己的handleChangeWrapper放入到監聽者鏈表中 */
trySubscribe() {
/*
parentSub 即是provide value 裏面的 Subscription 這裏可以理解爲 父級元素的 Subscription
*/
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.handleChangeWrapper)
/* provider的Subscription是不存在parentSub,所以此時trySubscribe 就會調用 store.subscribe */
: this.store.subscribe(this.handleChangeWrapper)
this.listeners = createListenerCollection()
}
}
/* 取消訂閱 */
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
this.listeners.clear()
this.listeners = nullListeners
}
}
}
發佈訂閱模式的實現
Subscription 的作用就是先通過trySubscribe發起訂閱模式,如果存在這父級訂閱者,就把自己更新函數handleChangeWrapper,傳遞給父級訂閱者,然後父級由addNestedSub 將此時的回調函數(更新函數)添加到當前的listeners中 。如果沒有父級元素,則將此回調函數放在store.subscribe中,我們要確定的一點是什麼情況下,不存在父級Subscription,我們這裏姑且認爲只有在provider父級Subscription不存在父級,那此時的handleChangeWrapper 函數中onStateChange,就是父級Subscription的notifyNestedSubs方法,而notifyNestedSubs方法會通知listens的notify方法來觸發更新,之前我們說了子代會把更新自身的handleChangeWrapper傳遞給parentSub,來觸發每一個connect組件更新。
這裏我們弄明白一個問題
react-redux更新組件也是用了store.subscribe 而且store.subscribe只用在了父級Subscription(沒有parentsub)中
大致模型就是 state更改 -> store.subscribe -> 觸發父級Subscription的handleChangeWrapper 也就是notifyNestedSubs -> 通知listeners.notify()->通知每個被connect容器組件的更新->callback執行->觸發子Subscription的handleChangeWrapper->觸發子Subscription的onstatechange(可以提前透漏一下,onstatechange保存了更新組件的函數)
前邊的內容提到了createListenerCollection,listeners,但是他具體有什麼作用我們接下來一起看一下。
function createListenerCollection() {
/* batch 由getBatch得到的 unstable_batchedUpdates 方法 */
const batch = getBatch()
let first = null
let last = null
return {
/* 清除當前listeners的所有listener */
clear() {
first = null
last = null
},
/* 派發更新 */
notify() {
batch(() => {
let listener = first
while (listener) {
listener.callback()
listener = listener.next
}
})
},
/* 獲取listeners的所有listener */
get() {
let listeners = []
let listener = first
while (listener) {
listeners.push(listener)
listener = listener.next
}
return listeners
},
/* 接收訂閱,將當前的callback(handleChangeWrapper)存到當前的鏈表中 */
subscribe(callback) {
let isSubscribed = true
let listener = (last = {
callback,
next: null,
prev: last
})
if (listener.prev) {
listener.prev.next = listener
} else {
first = listener
}
/* 取消當前 handleChangeWrapper 的訂閱*/
return function unsubscribe() {
if (!isSubscribed || first === null) return
isSubscribed = false
if (listener.next) {
listener.next.prev = listener.prev
} else {
last = listener.prev
}
if (listener.prev) {
listener.prev.next = listener.next
} else {
first = listener.next
}
}
}
}
}
我們可以得出結論createListenerCollection 可以產生一個listeners,listeners的作用。
1收集訂閱: 已鏈表的形式收集對應的listeners(每一個Subscription) 的handleChangeWrapper函數。
2派發更新: 通過batch方法( react-dom中的unstable_batchedUpdates) 來進行批量更新。
##總結
到這裏我們明白了
1 react-redux中的 provider 作用 ,通過react的context傳遞 subscription 和 redux中的store,並且建立了一個最頂部根Subscription。
2 Subscription 的作用:起到發佈訂閱作用,一方面訂閱connect包裹組件的更新函數,一方面通過store.subscribe統一派發更新。
3 Subscription如果存在這父級的情況,會把自身的更新函數,傳遞給父級Subscription來統一訂閱。
那麼隨之帶來的問題就是:
1 connect是怎麼樣連接我們的業務組件,然後傳遞我們組件更新函數的呢,更新函數本質是?
2 connect是怎麼通過第一個參數,來訂閱與之對應的state的呢?
3 connect怎麼樣將props,和redux的state合併的。
…
帶着這些問題,希望能在後續的文章中和大家共同探討~