<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          「源碼解析」一文吃透react-redux源碼(useMemo經(jīng)典源碼級案例)

          共 31313字,需瀏覽 63分鐘

           ·

          2021-05-08 05:29

          前言

          使用過redux的同學都知道,redux作為react公共狀態(tài)管理工具,配合react-redux可以很好的管理數(shù)據(jù),派發(fā)更新,更新視圖渲染的作用,那么對于 react-redux 是如何做到根據(jù) state 的改變,而更新組件,促使視圖渲染的呢,讓我們一起來探討一下,react-redux 源碼的奧妙所在。

          在正式分析之前我們不妨來想幾個問題:

          1 為什么要在 root 根組件上使用 react-redux 的 Provider 組件包裹?
          react-redux 是怎么和 redux 契合,做到 state 改變更新視圖的呢?
          provide 用什么方式存放當前的 redux 的 store, 又是怎么傳遞給每一個需要管理state的組件的?
          connect 是怎么樣連接我們的業(yè)務組件,然后傳遞我們組件更新函數(shù)的呢?
          connect 是怎么通過第一個參數(shù),來訂閱與之對應的 state 的呢?
          connect 怎么樣將 props,和 redux的 state 合并的?

          帶著這些疑問我們不妨先看一下 Provider 究竟做了什么?

          一 Provider 創(chuàng)建Subscription,context保存上下文


          /* provider 組件代碼 */function Provider({ store, context, children }) {   /* 利用useMemo,跟據(jù)store變化創(chuàng)建出一個contextValue 包含一個根元素訂閱器和當前store  */   const contextValue = useMemo(() => {      /* 創(chuàng)建了一個根 Subscription 訂閱器 */    const subscription = new Subscription(store)    /* subscription 的 notifyNestedSubs 方法 ,賦值給  onStateChange方法 */    subscription.onStateChange = subscription.notifyNestedSubs      return {      store,      subscription    } /*  store 改變創(chuàng)建新的contextValue */  }, [store])  /*  獲取更新之前的state值 ,函數(shù)組件里面的上下文要優(yōu)先于組件更新渲染  */  const previousState = useMemo(() => store.getState(), [store])
          useEffect(() => { const { subscription } = contextValue /* 觸發(fā)trySubscribe方法執(zhí)行,創(chuàng)建listens */ subscription.trySubscribe() // 發(fā)起訂閱 if (previousState !== store.getState()) { /* 組件更新渲染之后,如果此時state發(fā)生改變,那么立即觸發(fā) subscription.notifyNestedSubs 方法 */ subscription.notifyNestedSubs() } /* */ return () => { subscription.tryUnsubscribe() // 卸載訂閱 subscription.onStateChange = null } /* contextValue state 改變出發(fā)新的 effect */ }, [contextValue, previousState])
          const Context = context || ReactReduxContext /* context 存在用跟元素傳進來的context ,如果不存在 createContext創(chuàng)建一個context ,這里的ReactReduxContext就是由createContext創(chuàng)建出的context */ return <Context.Provider value={contextValue}>{children}</Context.Provider>}

          從源碼中provider作用大致是這樣的

          1 首先創(chuàng)建一個 contextValue ,里面包含一個創(chuàng)建出來的父級 Subscription (我們姑且先稱之為根級訂閱器)和redux提供的store。
          2 通過react上下文context把 contextValue 傳遞給子孫組件。

          二 Subscription訂閱消息,發(fā)布更新

          在我們分析了不是很長的 provider 源碼之后,隨之一個 Subscription 出現(xiàn),那么這個 Subscription 由什么作用呢??????,我們先來看看在 Provder 里出現(xiàn)的Subscription 方法。

          notifyNestedSubs trySubscribe tryUnsubscribe

          在整個 react-redux 執(zhí)行過程中 Subscription 作用非常重要,這里方便先透漏一下,他的作用是收集所有被 connect 包裹的組件的更新函數(shù) onstatechange,然后形成一個 callback 鏈表,再由父級 Subscription 統(tǒng)一派發(fā)執(zhí)行更新,我們暫且不關(guān)心它是怎么運作的,接下來就是 Subscription 源碼 ,我們重點看一下如上出現(xiàn)的三個方法。


          /* 發(fā)布訂閱者模式 */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發(fā)布通知 */ notifyNestedSubs() { this.listeners.notify() } /* 對于 provide onStateChange 就是 notifyNestedSubs 方法,對于 connect 包裹接受更新的組件 ,onStateChange 就是 負責更新組件的函數(shù) 。*/ handleChangeWrapper() { if (this.onStateChange) { this.onStateChange() } } /* 判斷有沒有開啟訂閱 */ isSubscribed() { return Boolean(this.unsubscribe) } /* 開啟訂閱模式 首先判斷當前訂閱器有沒有父級訂閱器 , 如果有父級訂閱器(就是父級Subscription),把自己的handleChangeWrapper放入到監(jiān)聽者鏈表中 */ trySubscribe() { /* parentSub 即是provide value 里面的 Subscription 這里可以理解為 父級元素的 Subscription */ if (!this.unsubscribe) { this.unsubscribe = this.parentSub ? this.parentSub.addNestedSub(this.handleChangeWrapper) /* provider的Subscription是不存在parentSub,所以此時trySubscribe 就會調(diào)用 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 } }}

          看完 Provider 和 Subscription源碼,我來解釋一下兩者到底有什么關(guān)聯(lián),首先Provider創(chuàng)建 Subscription 時候沒有第二個參數(shù),就說明provider 中的Subscription 不存在 parentSub 。那么再調(diào)用Provider組件中useEffect鉤子中trySubscribe的時候,會觸發(fā)this.store.subscribe , subscribe 就是 redux 的 subscribe ,此時真正發(fā)起了訂閱。

          subscription.onStateChange = subscription.notifyNestedSubs

          有此可知,最終state改變,觸發(fā)的是notifyNestedSubs方法。我們再一次看看這個notifyNestedSubs

          /* 向listeners發(fā)布通知 */notifyNestedSubs() {  this.listeners.notify()}

          最終向當前Subscription 的訂閱者們發(fā)布 notify更新。

          Subscription總結(jié) - 發(fā)布訂閱模式的實現(xiàn)

          綜上所述我們總結(jié)一下。Subscription 的作用,首先通過 trySubscribe 發(fā)起訂閱模式,如果存在這父級訂閱者,就把自己更新函數(shù)handleChangeWrapper,傳遞給父級訂閱者,然后父級由 addNestedSub 方法將此時的回調(diào)函數(shù)(更新函數(shù))添加到當前的 listeners 中 。如果沒有父級元素(Provider的情況),則將此回調(diào)函數(shù)放在store.subscribe中,handleChangeWrapper 函數(shù)中onStateChange,就是 Provider 中 Subscription 的 notifyNestedSubs 方法,而 notifyNestedSubs 方法會通知listens 的 notify 方法來觸發(fā)更新。這里透漏一下,子代Subscription會把更新自身handleChangeWrapper傳遞給parentSub,來統(tǒng)一通知connect組件更新。

          這里我們弄明白一個問題

          react-redux 更新組件也是用了 store.subscribe 而且 store.subscribe 只用在了 Provider 的 Subscription中 (沒有 parentsub )

          大致模型就是

          state更改 -> store.subscribe -> 觸發(fā) provider 的 Subscription 的 handleChangeWrapper 也就是 notifyNestedSubs -> 通知 listeners.notify() -> 通知每個被 connect 容器組件的更新 -> callback 執(zhí)行 -> 觸發(fā)子組件Subscription 的 handleChangeWrapper ->觸發(fā)子 onstatechange(可以提前透漏一下,onstatechange保存了更新組件的函數(shù))。

          前邊的內(nèi)容提到了**createListenerCollection,listeners**,但是他具體有什么作用我們接下來一起看一下。

          function createListenerCollection() {   /* batch 由getBatch得到的 unstable_batchedUpdates 方法 */  const batch = getBatch()  let first = null  let last = null
          return { /* 清除當前l(fā)isteners的所有l(wèi)istener */ clear() { first = null last = null }, /* 派發(fā)更新 */ notify() { batch(() => { let listener = first while (listener) { listener.callback() listener = listener.next } }) }, /* 獲取listeners的所有l(wèi)istener */ 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 } } } }}

          batch

          import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates'setBatch(batch)

          ?

          我們可以得出結(jié)論 createListenerCollection 可以產(chǎn)生一個 listeners 。 listeners的作用。

          1收集訂閱:以鏈表的形式收集對應的 listeners (每一個Subscription) 的handleChangeWrapper函數(shù)。
          2派發(fā)更新:, 通過 batch 方法( react-dom 中的 unstable_batchedUpdates ) 來進行批量更新。

          溫馨提示: React 的 unstable_batchedUpdate() API 允許將一次事件循環(huán)中的所有 React 更新都一起批量處理到一個渲染過程中。

          總結(jié)

          ??到這里我們明白了:

          react-redux 中的 provider 作用 ,通過 react 的 context 傳遞 subscription 和 redux 中的store ,并且建立了一個最頂部根 Subscription 。

          Subscription 的作用:起到發(fā)布訂閱作用,一方面訂閱 connect 包裹組件的更新函數(shù),一方面通過 store.subscribe 統(tǒng)一派發(fā)更新。

          Subscription 如果存在這父級的情況,會把自身的更新函數(shù),傳遞給父級 Subscription 來統(tǒng)一訂閱。

          三 connect 究竟做了什么?

          1 回顧 connect 用法

          工慾善其事,必先利其器 ,想要吃透源碼之前,必須深度熟悉其用法。才能知其然知其所以然。我們先來看看高階組件connect用法。

          function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?

          mapStateToProps

          const mapStateToProps = state => ({ todos: state.todos })

          作用很簡單,組件依賴redux的 state,映射到業(yè)務組件的 props中,state改變觸發(fā),業(yè)務組件props改變,觸發(fā)業(yè)務組件更新視圖。當這個參數(shù)沒有的時候,當前組件不會訂閱 store 的改變。

          mapDispatchToProps

          const mapDispatchToProps = dispatch => {  return {    increment: () => dispatch({ type: 'INCREMENT' }),    decrement: () => dispatch({ type: 'DECREMENT' }),    reset: () => dispatch({ type: 'RESET' })  }}


          將 redux 中的dispatch 方法,映射到,業(yè)務組件的props中。

          mergeProps

          /** stateProps , state 映射到 props 中的內(nèi)容* dispatchProps, dispatch 映射到 props 中的內(nèi)容。* ownProps 組件本身的 props*/(stateProps, dispatchProps, ownProps) => Object


          正常情況下,如果沒有這個參數(shù),會按照如下方式進行合并,返回的對象可以是,我們自定義的合并規(guī)則。我們還可以附加一些屬性。

          { ...ownProps, ...stateProps, ...dispatchProps }

          options

          {  context?: Object,   // 自定義上下文  pure?: boolean, // 默認為 true , 當為 true 的時候 ,除了 mapStateToProps 和 props ,其他輸入或者state 改變,均不會更新組件。  areStatesEqual?: Function, // 當pure true , 比較引進store 中state值 是否和之前相等。(next: Object, prev: Object) => boolean  areOwnPropsEqual?: Function, // 當pure true , 比較 props 值, 是否和之前相等。(next: Object, prev: Object) => boolean  areStatePropsEqual?: Function, // 當pure true , 比較 mapStateToProps 后的值 是否和之前相等。(next: Object, prev: Object) => boolean  areMergedPropsEqual?: Function, // 當 pure 為 true 時, 比較 經(jīng)過 mergeProps 合并后的值 , 是否與之前等  (next: Object, prev: Object) => boolean  forwardRef?: boolean, //當為true 時候,可以通過ref 獲取被connect包裹的組件實例。}


          options可以是如上屬性,上面已經(jīng)標注了每一個屬性的作用,這里就不多說了。

          2 connect 初探

          對于connect 組件 ,我們先看源碼一探究竟

          /src/connect/connect.js

          export function createConnect({  connectHOC = connectAdvanced,  mapStateToPropsFactories = defaultMapStateToPropsFactories,  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,  mergePropsFactories = defaultMergePropsFactories,  selectorFactory = defaultSelectorFactory} = {}) {  return function connect(    mapStateToProps,    mapDispatchToProps,    mergeProps,    {      pure = true,      areStatesEqual = strictEqual,      areOwnPropsEqual = shallowEqual,      areStatePropsEqual = shallowEqual,      areMergedPropsEqual = shallowEqual,      ...extraOptions    } = {}) {        /* 經(jīng)過代理包裝后的 mapStateToProps */    const initMapStateToProps = match( mapStateToProps, mapStateToPropsFactories,'mapStateToProps' )    /* 經(jīng)過代理包裝后的 mapDispatchToProps */    const initMapDispatchToProps = match(  mapDispatchToProps, mapDispatchToPropsFactories,'mapDispatchToProps')     /* 經(jīng)過代理包裝后的 mergeProps */    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
          return connectHOC(selectorFactory, { methodName: 'connect', getDisplayName: name => `Connect(${name})`, shouldHandleStateChanges: Boolean(mapStateToProps), initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, ...extraOptions }) }}
          export default /*#__PURE__*/ createConnect()


          我們先來分析一下整個函數(shù)做的事。

          1 首先定一個 createConnect方法。傳入了幾個默認參數(shù),有兩個參數(shù)非常重要,connectHOC 作為整個 connect 的高階組件。selectorFactory 做為整合connect更新過程中的形成新props的主要函數(shù)。默認的模式是pure模式。

          2 然后執(zhí)行createConnect方法,返回真正的connect函數(shù)本身。connect接收幾個參數(shù),然后和默認的函數(shù)進行整合,包裝,代理,最后形成三個真正的初始化函數(shù),這里的過程我們就先不講了。我們接下來分別介紹這三個函數(shù)的用途。

          initMapStateToProps ,用于形成真正的 MapStateToProps函數(shù),將 store 中 state ,映射到 props

          initMapDispatchToProps,用于形成真正的 MapDispatchToProps,將 dispatch 和 自定義的 dispatch 注入到props。

          initMergeProps,用于形成真正的 mergeProps函數(shù),合并業(yè)務組件的 props , state 映射的 props , dispatch 映射的 props。

          這里有一個函數(shù)非常重要,這個函數(shù)就是mergeProps, 請大家記住這個函數(shù),因為這個函數(shù)是判斷整個connect是否更新組件關(guān)鍵所在。上邊說過 connect基本用法的時候說過,當我們不向connect傳遞第三個參數(shù)mergeProps 的時候,默認的defaultMergeProps如下

          /src/connect/mergeProps.js

          export function defaultMergeProps(stateProps, dispatchProps, ownProps) {  return { ...ownProps, ...stateProps, ...dispatchProps }}


          這個函數(shù)返回了一個新的對象,也就是新的props。而且將 業(yè)務組件 props , store 中的 state ,和 dispatch 結(jié)合到一起,形成一個新對象,作為新的 props 傳遞給了業(yè)務組件。

          3 selectorFactory 形成新的props

          前面說到selectorFactory 很重要,用于形成新的props,我們記下來看selectorFactory 源碼。

          /src/connect/selectorFactory.js

          export default function finalPropsSelectorFactory(  dispatch,  { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }) {  // mapStateToProps mapDispatchToProps mergeProps 為真正connect 經(jīng)過一層代理的 proxy 函數(shù)  const mapStateToProps = initMapStateToProps(dispatch, options)  const mapDispatchToProps = initMapDispatchToProps(dispatch, options)  const mergeProps = initMergeProps(dispatch, options)
          const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory // 返回一個 函數(shù)用于生成新的 props return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options )}


          finalPropsSelectorFactory 的代碼很簡單, 首先得到真正connect 經(jīng)過一層代理函數(shù) mapStateToProps ,mapDispatchToProps ,mergeProps。然后調(diào)用selectorFactory (在pure模式下,selectorFactory 就是 pureFinalPropsSelectorFactory ) 。

          可以這里反復用了閉包,可以剛開始有點蒙,不過靜下心來看發(fā)現(xiàn)其實不是很難。由于默認是pure,所以我們接下來主要看 pureFinalPropsSelectorFactory 函數(shù)做了些什么。

          /** pure組件處理 , 對比 props 是否發(fā)生變化 然后 合并props */export function pureFinalPropsSelectorFactory(  mapStateToProps,  mapDispatchToProps,  mergeProps,  dispatch,  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual } //判斷 state prop 是否相等) {  let hasRunAtLeastOnce = false  let state  let ownProps  let stateProps  let dispatchProps  let mergedProps   /* 第一次 直接形成 ownProps  stateProps  dispatchProps 合并  形成新的 props */  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() {    //  props 和 state 都改變  mergeProps   }
          function handleNewProps() { // props 改變 mergeProps }
          function handleNewState() { // state 改變 mergeProps }
          /* 不是第一次的情況 props 或者 store.state 發(fā)生改變的情況。*/ function handleSubsequentCalls(nextState, nextOwnProps) { /* 判斷兩次 props 是否相等 */ const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) /* 判斷兩次 store.state 是否相等 */ 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) }}

          這個函數(shù)處理邏輯很清晰。大致上做了這些事。通過閉包的形式返回一個函數(shù)pureFinalPropsSelectorpureFinalPropsSelector通過判斷是否是第一次初始化組件。

          如果是第一次,那么直接調(diào)用mergeProps合并ownProps,stateProps,dispatchProps 形成最終的props。如果不是第一次,那么判斷到底是props還是 store.state 發(fā)生改變,然后針對那里變化,重新生成對應的props,最終合并到真正的props。

          整個 selectorFactory 邏輯就是形成新的props傳遞給我們的業(yè)務組件。

          4 connectAdvanced 形成真正包裹業(yè)務組件的 Hoc

          接下來我們看一下 connect 返回的 connectAdvanced()到底做了什么,為了方便大家理解connect,我們這里先看看 connect 用法。

          正常模式下:

          const mapStateToProp = (store) => ({ userInfo: store.root.userInfo })
          function Index(){ /* ..... */ return <div> { /* .... */ } </div>}export default connect(mapStateToProp)(Index)

          裝飾器模式下:

          const mapStateToProp = (store) => ({ userInfo: store.root.userInfo })
          @connect(mapStateToProp)class Index extends React.Component{ /* .... */ render(){ return <div> { /* .... */ } </div> }}

          我們上面講到,connect執(zhí)行 接受 mapStateToProp 等參數(shù),最后返回 connectAdvanced() ,那么上述例子中connect執(zhí)行第一步connect(mapStateToProp)===connectAdvanced(),也就是connectAdvanced()執(zhí)行返回真正的hoc,用于包裹我們的業(yè)務組件。

          接下來我們看 connectAdvanced 代碼

          /src/components/connectAdvanced.js

          export default function connectAdvanced(  selectorFactory, // 每次 props,state改變執(zhí)行 ,用于生成新的 props。  {    getDisplayName = name => `ConnectAdvanced(${name})`,    //可能被包裝函數(shù)(如connect())重寫    methodName = 'connectAdvanced',    //如果定義了,則傳遞給包裝元素的屬性的名稱,指示要呈現(xiàn)的調(diào)用。用于監(jiān)視react devtools中不必要的重新渲染。    renderCountProp = undefined,    shouldHandleStateChanges = true,  //確定此HOC是否訂閱存儲更改    storeKey = 'store',    withRef = false,    forwardRef = false, // 是否 用 forwarRef 模式    context = ReactReduxContext,// Provider 保存的上下文    ...connectOptions  } = {}) {  /* ReactReduxContext 就是store存在的context */  const Context = context   /* WrappedComponent 為connect 包裹的組件本身  */     return  function wrapWithConnect(WrappedComponent){      // WrappedComponent 被 connect 的業(yè)務組件本身  }}


          connectAdvanced接受配置參數(shù) , 然后返回真正的 HOC wrapWithConnect。

          // 我們可以講下面的表達式分解connect(mapStateToProp)(Index)
          // 執(zhí)行 connectconnect(mapStateToProp) //返回 connectAdvanced()//返回HOCwrapWithConnect

          接下來我們分析一下wrapWithConnect到底做了些什么?

          5 wrapWithConnect 高階組件

          接下來我們來一起研究一下 wrapWithConnect,我們重點看一下 wrapWithConnect作為高階組件,會返回一個組件,這個組件會對原有的業(yè)務組件,進行一系列增強等工作。

          function wrapWithConnect(WrappedComponent) {    const wrappedComponentName =      WrappedComponent.displayName || WrappedComponent.name || 'Component'      const displayName = getDisplayName(wrappedComponentName)      const selectorFactoryOptions = {      ...connectOptions,      getDisplayName,      methodName,      renderCountProp,      shouldHandleStateChanges,      storeKey,      displayName,      wrappedComponentName,      WrappedComponent    }    const { pure } = connectOptions    function createChildSelector(store) {      // 合并函數(shù) mergeprops 得到最新的props      return selectorFactory(store.dispatch, selectorFactoryOptions)    }    //判斷是否是pure純組件模式 如果是 將用 useMemo 提升性能    const usePureOnlyMemo = pure ? useMemo : callback => callback()    // 負責更新的容器子組件    function ConnectFunction (props){        // props 為 業(yè)務組件 真正的 props     }    const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction      Connect.WrappedComponent = WrappedComponent    Connect.displayName = displayName    /* forwardRef */    if (forwardRef) {      const forwarded = React.forwardRef(function forwardConnectRef(        props,        ref) {        return <Connect {...props} reactReduxForwardedRef={ref} />      })        forwarded.displayName = displayName      forwarded.WrappedComponent = WrappedComponent      return hoistStatics(forwarded, WrappedComponent)    }      return hoistStatics(Connect, WrappedComponent)  }}

          wrapWithConnect 的做的事大致分為一下幾點:

          第一步

          1 聲明負責更新的 ConnectFunction 無狀態(tài)組件。和負責合并 props 的createChildSelector方法

          第二步

          2 判斷是否是 pure 純組件模式,如果是用react.memo包裹,這樣做的好處是,會向 pureComponent 一樣對 props 進行淺比較。

          第三步

          3 如果 connect 有forwardRef配置項,用React.forwardRef處理,這樣做好處如下。

          正常情況下因為我們的WrappedComponent 被 connect 包裝,所以不能通過ref訪問到業(yè)務組件WrappedComponent的實例。

          子組件

          const mapStateToProp = (store) => ({ userInfo: store.root.userInfo })
          class Child extends React.Component{ render(){ /* ... */ }}export default connect(mapStateToProp)(Child)


          父組件

          class Father extends React.Compoent{    child = null     render(){        return <Child ref={(cur)=> this.child = cur }  { /* 獲取到的不是`Child`本身 */ } />    }}


          我們無法通過 ref 訪問到 Child 組件。

          所以我們可以通過 options 的 forwardRef 屬性設置為 true,這樣就可以根本解決問題。

          connect(mapStateToProp,mapDispatchToProps,mergeProps,{ forwardRef:true  })(Child)

          第四步

          hoistStatics(Connect, WrappedComponent)

          最后做的事情就是通過hoistStatics庫 把子組件WrappedComponent的靜態(tài)方法/屬性,繼承到父組件Connect上。因為在 高階組件 包裝 業(yè)務組件的過程中,如果不對靜態(tài)屬性或是方法加以額外處理,是不會被包裝后的組件訪問到的,所以需要類似hoistStatics這樣的庫,來做處理。

          接下來講的就是整個 connect的核心了。我們來看一下負責更新的容器ConnectFunction 到底做了些什么?

          6 ConnectFunction 控制更新

          ConnectFunction 的代碼很復雜,需要我們一步步去吃透,一步步去消化。


            function ConnectFunction(props) {      /* TODO:  第一步 把 context ForwardedRef props 取出來 */      const [        reactReduxForwardedRef,        wrapperProps // props 傳遞的props      ] = useMemo(() => {               const { reactReduxForwardedRef, ...wrapperProps } = props        return [reactReduxForwardedRef, wrapperProps]      }, [props])           // 獲取 context內(nèi)容 里面含有  redux 中store 和 subscription      const contextValue = useContext(Context)
          //TODO: 判斷 store 是否來此 props didStoreComeFromProps ,正常情況下 ,prop 中是不存在 store 所以 didStoreComeFromProps = false const didStoreComeFromProps = Boolean(props.store) && Boolean(props.store.getState) && Boolean(props.store.dispatch) const didStoreComeFromContext = Boolean(contextValue) && Boolean(contextValue.store) // 獲取 redux 中 store const store = didStoreComeFromProps ? props.store : contextValue.store // 返回merge函數(shù) 用于生成真正傳給子組件 props const childPropsSelector = useMemo(() => { return createChildSelector(store) }, [store])

          // TODO: 第二步 subscription 監(jiān)聽者實例 const [subscription, notifyNestedSubs] = useMemo(() => { // 如果沒有訂閱更新,那么直接返回。 if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY const subscription = new Subscription( store, didStoreComeFromProps ? null : contextValue.subscription // 和 上級 `subscription` 建立起關(guān)系。this.parentSub = contextValue.subscription ) // notifyNestedSubs 觸發(fā) noticy 所有子代 listener 監(jiān)聽者 -> 觸發(fā)batch方法,觸發(fā) batchupdate方法 ,批量更新 const notifyNestedSubs = subscription.notifyNestedSubs.bind( subscription ) return [subscription, notifyNestedSubs] }, [store, didStoreComeFromProps, contextValue])
          /* 創(chuàng)建出一個新的contextValue ,把父級的 subscription 換成自己的 subscription */ const overriddenContextValue = useMemo(() => { if (didStoreComeFromProps) { return contextValue } return { ...contextValue, subscription } }, [didStoreComeFromProps, contextValue, subscription]) const [ [previousStateUpdateResult], forceComponentUpdateDispatch /* */ ] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)
          // TODO: 第三步 const lastChildProps = useRef() //保存上一次 合并過的 props信息(經(jīng)過 ownprops ,stateProps , dispatchProps 合并過的 ) const lastWrapperProps = useRef(wrapperProps) // 保存本次上下文執(zhí)行 業(yè)務組件的 props const childPropsFromStoreUpdate = useRef() const renderIsScheduled = useRef(false) // 當前組件是否處于渲染階段 // actualChildProps 為當前真正處理過后,經(jīng)過合并的 props const actualChildProps = usePureOnlyMemo(() => { // 調(diào)用 mergeProps 進行合并,返回合并后的最新 porps return childPropsSelector(store.getState(), wrapperProps) }, [store, previousStateUpdateResult, wrapperProps])
          /* 負責更新緩存變量,方便下一次更新的時候比較 */ useEffect(()=>{ captureWrapperProps(...[ lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs ]) }) useEffect(()=>{ subscribeUpdates(...[ shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch ]) },[store, subscription, childPropsSelector])


          // TODO: 第四步:reactReduxForwardedRef 是處理父級元素是否含有 forwardRef 的情況 這里可以忽略。 const renderedWrappedComponent = useMemo( () => ( <WrappedComponent {...actualChildProps} ref={reactReduxForwardedRef} /> ), [reactReduxForwardedRef, WrappedComponent, actualChildProps] ) const renderedChild = useMemo(() => { //shouldHandleStateChanges 來源 connect是否有第一個參數(shù) if (shouldHandleStateChanges) { return ( // ContextToUse 傳遞 context <ContextToUse.Provider value={overriddenContextValue}> {renderedWrappedComponent} </ContextToUse.Provider> ) } return renderedWrappedComponent }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]) return renderedChild }

          為了方便大家更直觀的理解,我這里保留了影響流程的核心代碼,我會一步步分析 整個核心部分。想要弄明白這里,需要對 react-hooks 和 provider 有一些了解。

          第一步

          通過 props 分離出 reactReduxForwardedRef , wrapperProps 。reactReduxForwardedRef 是當開啟 ForwardedRef 模式下,父級傳過來的 React.forwaedRef。

          然后判斷通過常量didStoreComeFromProps儲存當前,redux.store 是否來自 props, 正常情況下,我們的 store 都來自 provider ,不會來自props,所以我們可以把didStoreComeFromProps = true 。接下來我們獲取到 store,通過 store 來判斷是否更新真正的合并props函數(shù)childPropsSelector。

          第二步 創(chuàng)建 子代 subscription, 層層傳遞新的 context(很重要)

          這一步非常重要,判斷通過shouldHandleStateChanges判斷此 HOC 是否訂閱存儲更改,如果已經(jīng)訂閱了更新(此時connect 具有第一個參數(shù)),那么創(chuàng)建一個 subscription ,并且和上一層providersubscription建立起關(guān)聯(lián)。this.parentSub = contextValue.subscription。然后分離出 subscription 和 notifyNestedSubs(notifyNestedSubs的作用是通知當前subscription的 listeners 進行更新的方法。) 。

          然后通過 useMemo 創(chuàng)建出一個新的 contextValue ,把父級的 subscription 換成自己的 subscription。用于通過 Provider 傳遞新的 context。這里簡單介紹一下,運用了 Provider 可以和多個消費組件有對應關(guān)系。多個 Provider 也可以嵌套使用,里層的會覆蓋外層的數(shù)據(jù)。react-reduxcontext更傾向于Provider良好的傳遞上下文的能力。

          接下來通過useReducer制造出真正觸發(fā)更新的forceComponentUpdateDispatch 函數(shù)。也就是整個 state 或者是 props改變,觸發(fā)組件更新的函數(shù)。為什么這么做呢?

          筆者認為react-redxx這樣設計原因是希望connect自己控制自己的更新,并且多個上下級 connect不收到影響。所以一方面通過useMemo來限制業(yè)務組件不必要的更新,另一方面來通過forceComponentUpdateDispatch來更新 HOC 函數(shù),產(chǎn)生actualChildProps,actualChildProps 改變 ,useMemo執(zhí)行,觸發(fā)組件渲染。

          第三步:保存信息,執(zhí)行副作用鉤子(最重要的部分到了)

          這一步十分重要,為什么這么說呢,首先先通過useRef緩存幾個變量:

          lastChildProps -> 保存上一次 合并過的 props 信息(經(jīng)過 ownprops ,stateProps , dispatchProps 合并過的 )。 lastWrapperProps -> 保存本次上下文執(zhí)行 業(yè)務組件的 props 。 renderIsScheduled -> 當前組件是否處于渲染階段。 actualChildProps -> actualChildProps 為當前真正處理過后,經(jīng)過合并的 props, 組件通過 dep -> actualChildProps,來判斷是否進行更新。

          接下來執(zhí)行兩次 useEffect , 源碼中不是這個樣子的,我這里經(jīng)過簡化,第一個 useEffect 執(zhí)行了 captureWrapperProps ,captureWrapperProps 是干什么的呢?

          //獲取包裝的props function captureWrapperProps(  lastWrapperProps,  lastChildProps,  renderIsScheduled,  wrapperProps,  actualChildProps,  childPropsFromStoreUpdate,  notifyNestedSubs) {  lastWrapperProps.current = wrapperProps  //子props   lastChildProps.current = actualChildProps //經(jīng)過 megeprops 之后形成的 prop  renderIsScheduled.current = false  // 當前組件渲染完成}

          captureWrapperProps 的作用很簡單,在一次組件渲染更新后,將上一次 合并前 和 合并后 的props,保存起來。這么做目的是,能過在兩次hoc執(zhí)行渲染中,對比props stateProps是否發(fā)生變化。從而確定是否更新 hoc,進一步更新組件。

          執(zhí)行第二個 useEffect 是很關(guān)鍵。執(zhí)行subscribeUpdates 函數(shù),subscribeUpdates 是訂閱更新的主要函數(shù),我們一起來看看:

          function subscribeUpdates(  shouldHandleStateChanges,  store,  subscription,  childPropsSelector,  lastWrapperProps,  //子props   lastChildProps, //經(jīng)過 megeprops 之后形成的 prop  renderIsScheduled,  childPropsFromStoreUpdate,  notifyNestedSubs,  forceComponentUpdateDispatch) {  if (!shouldHandleStateChanges) return
          // 捕獲值以檢查此組件是否卸載以及何時卸載 let didUnsubscribe = false let lastThrownError = null //store更新訂閱傳播到此組件時,運行此回調(diào) const checkForUpdates = ()=>{ //.... } subscription.onStateChange = checkForUpdates //開啟訂閱者 ,當前是被connect 包轉(zhuǎn)的情況 會把 當前的 checkForceUpdate 放在存入 父元素的addNestedSub中。 subscription.trySubscribe() //在第一次呈現(xiàn)之后從存儲中提取數(shù)據(jù),以防存儲從我們開始就改變了。 checkForUpdates() /* 卸載訂閱起 */ const unsubscribeWrapper = () => { didUnsubscribe = true subscription.tryUnsubscribe() subscription.onStateChange = null }
          return unsubscribeWrapper}

          這絕對是整個訂閱更新的核心,首先聲明 store 更新訂閱傳播到此組件時的回調(diào)函數(shù)checkForUpdates把它賦值給onStateChange,如果store中的state發(fā)生改變,那么在組件訂閱了state內(nèi)容之后,相關(guān)聯(lián)的state改變就會觸發(fā)當前組件的onStateChange,來合并得到新的props,從而觸發(fā)組件更新。

          然后subscription.trySubscribe()把訂閱函數(shù)onStateChange綁定給父級subscription,進行了層層訂閱。

          最后,為了防止渲染后,store內(nèi)容已經(jīng)改變,所以首先執(zhí)行了一次checkForUpdates。那么checkForUpdates的作用很明確了,就是檢查是否派發(fā)當前組件的更新。

          到這里我們明白了,react-redux 通過 subscription 進行層層訂閱。對于一層層的組件結(jié)構(gòu),整體模型圖如下:。

          接下來我們看一下checkForUpdates

            //store更新訂閱傳播到此組件時,運行此回調(diào)  const checkForUpdates = () => {    if (didUnsubscribe) {      //如果寫在了      return    }     // 獲取 store 里state    const latestStoreState = store.getState()q    let newChildProps, error    try {      /* 得到最新的 props */      newChildProps = childPropsSelector(        latestStoreState,        lastWrapperProps.current      )    }     //如果新的合并的 props沒有更改,則此處不做任何操作-層疊訂閱更新    if (newChildProps === lastChildProps.current) {       if (!renderIsScheduled.current) {          notifyNestedSubs() /* 通知子代 subscription 觸發(fā) checkForUpdates 來檢查是否需要更新。*/      }    } else {      lastChildProps.current = newChildProps      childPropsFromStoreUpdate.current = newChildProps      renderIsScheduled.current = true      // 此情況 可能考慮到 代碼運行到這里 又發(fā)生了 props 更新 所以觸發(fā)一個 reducer 來促使組件更新。      forceComponentUpdateDispatch({        type: 'STORE_UPDATED',        payload: {          error        }      })    }  }


          checkForUpdates 通過調(diào)用 childPropsSelector來形成新的props,然后判斷之前的 prop 和當前新的 prop 是否相等。如果相等,證明沒有發(fā)生變化,無須更新當前組件,那么通過調(diào)用notifyNestedSubs來通知子代容器組件,檢查是否需要更新。如果不相等證明訂閱的store.state發(fā)生變化,那么立即執(zhí)行forceComponentUpdateDispatch來觸發(fā)組件的更新。

          對于層層訂閱的結(jié)構(gòu),整個更新模型圖如下:

          總結(jié)

          接下來我們總結(jié)一下整個connect的流程。我們還是從訂閱更新兩個方向入手。

          訂閱流程

          整個訂閱的流程是,如果被connect包裹,并且具有第一個參數(shù)。首先通過context獲取最近的 subscription,然后創(chuàng)建一個新的subscription,并且和父級的subscription建立起關(guān)聯(lián)。當?shù)谝淮?code style="box-sizing: border-box;font-family: Menlo, Monaco, Consolas, "Courier New", monospace;font-size: 0.87em;word-break: break-word;border-radius: 2px;overflow-x: auto;background-color: rgb(255, 245, 245);color: rgb(255, 80, 44);padding: 0.065em 0.4em;">hoc容器組件掛在完成后,在useEffect里,進行訂閱,將自己的訂閱函數(shù)checkForUpdates,作為回調(diào)函數(shù),通過trySubscribe 和this.parentSub.addNestedSub ,加入到父級subscriptionlisteners中。由此完成整個訂閱流程。

          更新流程

          整個更新流程是,那state改變,會觸發(fā)根訂閱器的store.subscribe,然后會觸發(fā)listeners.notify ,也就是checkForUpdates函數(shù),然后checkForUpdates函數(shù)首先根據(jù)mapStoretopropsmergeprops等操作,驗證該組件是否發(fā)起訂閱,props 是否改變,并更新,如果發(fā)生改變,那么觸發(fā)useReducerforceComponentUpdateDispatch函數(shù),來更新業(yè)務組件,如果沒有發(fā)生更新,那么通過調(diào)用notifyNestedSubs,來通知當前subscriptionlisteners檢查是否更新,然后盡心層層checkForUpdates,逐級向下,借此完成整個更新流程。

          四 關(guān)于 useMemo 用法思考?

          整個react-redux源碼中,對于useMemo用法還是蠻多的,我總結(jié)了幾條,奉上????:

          1 緩存屬性 / 方法

          react-redux源碼中,多處應用了useMemo 依賴/緩存 屬性的情況。這樣做的好處是只有依賴項發(fā)生改變的時候,才更新新的緩存屬性/方法,比如 childPropsSelector , subscription , actualChildProps 等主要方法屬性。

          2 控制組件渲染,渲染節(jié)流。

          react-redux源碼中,通過 useMemo來控制業(yè)務組件是否渲染。通過 actualChildProps變化,來證明是否來自 **自身 props ** 或 訂閱的 state 的修改,來確定是否渲染組件。

          例子??:

          const renderedWrappedComponent = useMemo(    () => (        <WrappedComponent        {...actualChildProps}        ref={reactReduxForwardedRef}        />    ),    [reactReduxForwardedRef, WrappedComponent, actualChildProps])


          五 總結(jié)

          希望這篇文章能讓屏幕前的你,對react-redux的訂閱和更新流程有一個新的認識。送人玫瑰,手留余香,閱讀的朋友可以給筆者點贊,關(guān)注一波 ,陸續(xù)更新前端超硬核文章。


                        
          瀏覽 51
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色特一级黄片 | 亚洲午夜在线 | 人人干人人爱 | 粉嫩色网av网 | 中日韩无码视频 |