神奇的react-redux

1、引言

以前學習了Redux,而後發現有Redux的地方几乎都少不了react-redux這個庫,它能夠說是創建了React組件和Redux之間的橋樑。因此我特意學習了react-redux,以爲頗有必要記錄一下。react

2、爲何須要使用react-redux

若是不用react-redux的話,咱們想在React組件中使用Redux,就不得不引入store,使用store.dispatch(action)去分派action,間接地改變狀態。乍一看,這樣沒什麼問題啊,是的,若是隻是一個組件的話這樣確實沒什麼問題。可是,試想,若是該組件的子組件也想用名爲xxx的state的話,豈不是又得引入store,而後又是像父組件那樣一套流程下來,這樣確實生效,可是顯得代碼很是的臃腫,可維護性不好,由於作了不少重複的工做。可是若是咱們引入一個容器組件,這個組件可使用props的方式將state和dispatch傳遞給子組件,這樣就省去了不少重複的工做。這時,react-redux庫就發揮做用了,創建起React和store的橋樑,能夠將store和dispatch映射給props,這樣React的組件就能夠經過props將state和dispatch向下傳遞;或者可使用Provider提供一個context,將store無限制向下傳遞給子孫組件。redux

3、react-redux作了什麼

先說一下react-redux作了什麼:ide

  • 提供Subscrption類,實現訂閱更新的邏輯
  • 提供Provider,將store傳入Provider,便於下層組件從context或者props中獲取store;並訂閱store的變化,便於在store變化的時候執行被訂閱到react-redux內的更新函數
  • 提供selector,負責將獲取store中的state和dispacth一些action的函數(或者直接就是dispatch)或者組件本身的props,從中選擇出組件須要的值,做爲selector的返回值
  • 提供connect高階組件,主要作了兩件事:函數

    • 執行selector,獲取到要注入到組件中的值,將它們注入到組件的props
    • 訂閱props的變化,負責在props變化的時候更新組件

4、react-redux是怎麼作到的

當我瞭解到react-redux的大體功能以後,我腦海裏立馬產生了三個疑問,分別是:學習

  • Provider是如何將store放入context中的?
  • 如何將store中的state和dispatch(或調用dispatch的函數)注入組件中的props的?
  • React-Redux如何作到當store變化,被connect的組件也會更新的?(如何監測store的變化?)

5、解決以上提出的問題

1.Provider是怎麼把store放入context中的?

先來看Provider.js的源碼:ui

function Provider({ store, context, children }) {
  const contextValue = useMemo(() => {
    //聲明一個Subscription實例。訂閱,監聽state變化來執行listener,都由實例來完成
    const subscription = new Subscription(store)
    //綁定監聽,當state變化時,通知訂閱者更新頁面,實際上也就是在connect過程當中被訂閱到react-redux的subscription對象上的更新函數
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription
    }
  }, [store])

  //獲取當前的store的state,做爲上一次的state,將會在組件掛載完畢後,
  //與store新的state比較,不一致的話更新Provider組件
  const previousState = useMemo(() => store.getState(), [store])

  useEffect(() => {
    //這會在組件渲染以後執行,因此這個時候contextValue已經返回
    //在組件掛載完畢後,訂閱更新。
    const { subscription } = contextValue       //contextValue = { store, subscription }
    //這裏先理解爲最開始的時候須要訂閱更新函數,便於在狀態變化的時候執行更新函數,至關因而註冊了一個監聽,在監聽state的變化。
    subscription.trySubscribe()

    //若是先後的store中的state有變化,那麼就去更新Provider組件
    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs()
    }
    return () => {
      subscription.tryUnsubscribe()
      subscription.onStateChange = null
    }
  }, [contextValue, previousState])

  const Context = context || ReactReduxContext

  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

if (process.env.NODE_ENV !== 'production') {
  //propTypes僅在開發模式下進行檢查
  Provider.propTypes = {
    store: PropTypes.shape({
      subscribe: PropTypes.func.isRequired,
      dispatch: PropTypes.func.isRequired,
      getState: PropTypes.func.isRequired
    }),
    context: PropTypes.object,
    children: PropTypes.any
  }
}

export default Provider

因此結合代碼看這個問題:Provider是怎麼把store放入context中的,很好理解。
Provider最主要的功能是從props中獲取咱們傳入的store,並將store做爲context的其中一個值,向下層組件下發this

可是,一旦store變化,Provider要有所反應,以此保證將始終將最新的store放入context中。因此這裏要用訂閱來實現更新。天然引出Subscription類,經過該類的實例,將onStateChange監聽到一個可更新UI的事件this.notifySubscribers上:spa

subscription.onStateChange = this.notifySubscribers

組件掛載完成後,去訂閱更新,至於這裏訂閱的是什麼,要看Subscription的實現。這裏先給出結論:本質上訂閱的是onStateChange,實現訂閱的函數是:Subscription類以內的trySubscribe。code

this.state.subscription.trySubscribe()

再接着,若是先後的state不同,那麼就去通知訂閱者更新,onStateChange就會執行,Provider組件就會執行下層組件訂閱到react-redux的更新函數。當Provider更新完成(componentDidUpdate),會去比較一下先後的store是否相同,若是不一樣,那麼用新的store做爲context的值,而且取消訂閱,從新訂閱一個新的Subscription實例。保證用的數據都是最新的。component

//若是先後的store中的state有變化,那麼就去更新Provider組件
if (previousState !== store.getState()) {
  subscription.notifyNestedSubs()
}
return () => {
  subscription.tryUnsubscribe()
  subscription.onStateChange = null
}

//未完成...

相關文章
相關標籤/搜索