react-redux 源碼解析一: Provider做了什麼,發佈訂閱模式實現?

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合併的。

帶着這些問題,希望能在後續的文章中和大家共同探討~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章