1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 31313字,需瀏覽 63分鐘

         ·

        2021-05-08 05:29

        前言

        使用過redux的同學(xué)都知道,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 用什么方式存放當(dāng)前的 redux 的 store, 又是怎么傳遞給每一個需要管理state的組件的?
        connect 是怎么樣連接我們的業(yè)務(wù)組件,然后傳遞我們組件更新函數(shù)的呢?
        connect 是怎么通過第一個參數(shù),來訂閱與之對應(yīng)的 state 的呢?
        connect 怎么樣將 props,和 redux的 state 合并的?

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

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


        /* provider 組件代碼 */function Provider({ store, context, children }) {   /* 利用useMemo,跟據(jù)store變化創(chuàng)建出一個contextValue 包含一個根元素訂閱器和當(dāng)前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 存在用跟元素傳進(jìn)來的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)心它是怎么運(yùn)作的,接下來就是 Subscription 源碼 ,我們重點(diǎn)看一下如上出現(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) } /* 負(fù)責(zé)檢測是否該組件訂閱,然后添加訂閱者也就是listener */ addNestedSub(listener) { this.trySubscribe() return this.listeners.subscribe(listener) } /* 向listeners發(fā)布通知 */ notifyNestedSubs() { this.listeners.notify() } /* 對于 provide onStateChange 就是 notifyNestedSubs 方法,對于 connect 包裹接受更新的組件 ,onStateChange 就是 負(fù)責(zé)更新組件的函數(shù) 。*/ handleChangeWrapper() { if (this.onStateChange) { this.onStateChange() } } /* 判斷有沒有開啟訂閱 */ isSubscribed() { return Boolean(this.unsubscribe) } /* 開啟訂閱模式 首先判斷當(dāng)前訂閱器有沒有父級訂閱器 , 如果有父級訂閱器(就是父級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()}

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

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

        綜上所述我們總結(jié)一下。Subscription 的作用,首先通過 trySubscribe 發(fā)起訂閱模式,如果存在這父級訂閱者,就把自己更新函數(shù)handleChangeWrapper,傳遞給父級訂閱者,然后父級由 addNestedSub 方法將此時的回調(diào)函數(shù)(更新函數(shù))添加到當(dāng)前的 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 { /* 清除當(dāng)前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 }, /* 接收訂閱,將當(dāng)前的callback(handleChangeWrapper)存到當(dāng)前的鏈表中 */ subscribe(callback) { let isSubscribed = true
        let listener = (last = { callback, next: null, prev: last })
        if (listener.prev) { listener.prev.next = listener } else { first = listener } /* 取消當(dāng)前 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收集訂閱:以鏈表的形式收集對應(yīng)的 listeners (每一個Subscription) 的handleChangeWrapper函數(shù)。
        2派發(fā)更新:, 通過 batch 方法( react-dom 中的 unstable_batchedUpdates ) 來進(jìn)行批量更新。

        溫馨提示: 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è)務(wù)組件的 props中,state改變觸發(fā),業(yè)務(wù)組件props改變,觸發(fā)業(yè)務(wù)組件更新視圖。當(dāng)這個參數(shù)沒有的時候,當(dāng)前組件不會訂閱 store 的改變。

        mapDispatchToProps

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


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

        mergeProps

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


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

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

        options

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


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

        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方法。傳入了幾個默認(rèn)參數(shù),有兩個參數(shù)非常重要,connectHOC 作為整個 connect 的高階組件。selectorFactory 做為整合connect更新過程中的形成新props的主要函數(shù)。默認(rèn)的模式是pure模式。

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

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

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

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

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

        /src/connect/mergeProps.js

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


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

        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ù)用了閉包,可以剛開始有點(diǎn)蒙,不過靜下心來看發(fā)現(xiàn)其實不是很難。由于默認(rè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ù)pureFinalPropsSelector。pureFinalPropsSelector通過判斷是否是第一次初始化組件。

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

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

        4 connectAdvanced 形成真正包裹業(yè)務(wù)組件的 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è)務(wù)組件。

        接下來我們看 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è)務(wù)組件本身  }}


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

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

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

        5 wrapWithConnect 高階組件

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

        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()    // 負(fù)責(zé)更新的容器子組件    function ConnectFunction (props){        // props 為 業(yè)務(wù)組件 真正的 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 的做的事大致分為一下幾點(diǎn):

        第一步

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

        第二步

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

        第三步

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

        正常情況下因為我們的WrappedComponent 被 connect 包裝,所以不能通過ref訪問到業(yè)務(wù)組件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 屬性設(shè)置為 true,這樣就可以根本解決問題。

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

        第四步

        hoistStatics(Connect, WrappedComponent)

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

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

        6 ConnectFunction 控制更新

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


          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è)務(wù)組件的 props const childPropsFromStoreUpdate = useRef() const renderIsScheduled = useRef(false) // 當(dāng)前組件是否處于渲染階段 // actualChildProps 為當(dāng)前真正處理過后,經(jīng)過合并的 props const actualChildProps = usePureOnlyMemo(() => { // 調(diào)用 mergeProps 進(jìn)行合并,返回合并后的最新 porps return childPropsSelector(store.getState(), wrapperProps) }, [store, previousStateUpdateResult, wrapperProps])
        /* 負(fù)責(zé)更新緩存變量,方便下一次更新的時候比較 */ 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 是當(dāng)開啟 ForwardedRef 模式下,父級傳過來的 React.forwaedRef

        然后判斷通過常量didStoreComeFromProps儲存當(dāng)前,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的作用是通知當(dāng)前subscription的 listeners 進(jìn)行更新的方法。) 。

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

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

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

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

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

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

        接下來執(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  // 當(dāng)前組件渲染完成}

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

        執(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更新訂閱傳播到此組件時,運(yùn)行此回調(diào) const checkForUpdates = ()=>{ //.... } subscription.onStateChange = checkForUpdates //開啟訂閱者 ,當(dāng)前是被connect 包轉(zhuǎn)的情況 會把 當(dāng)前的 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ā)當(dāng)前組件的onStateChange,來合并得到新的props,從而觸發(fā)組件更新。

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

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

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

        接下來我們看一下checkForUpdates

          //store更新訂閱傳播到此組件時,運(yùn)行此回調(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      // 此情況 可能考慮到 代碼運(yùn)行到這里 又發(fā)生了 props 更新 所以觸發(fā)一個 reducer 來促使組件更新。      forceComponentUpdateDispatch({        type: 'STORE_UPDATED',        payload: {          error        }      })    }  }


        checkForUpdates 通過調(diào)用 childPropsSelector來形成新的props,然后判斷之前的 prop 和當(dāng)前新的 prop 是否相等。如果相等,證明沒有發(fā)生變化,無須更新當(dāng)前組件,那么通過調(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)。當(dāng)?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里,進(jìn)行訂閱,將自己的訂閱函數(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è)務(wù)組件,如果沒有發(fā)生更新,那么通過調(diào)用notifyNestedSubs,來通知當(dāng)前subscriptionlisteners檢查是否更新,然后盡心層層checkForUpdates,逐級向下,借此完成整個更新流程。

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

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

        1 緩存屬性 / 方法

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

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

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

        例子??:

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


        五 總結(jié)

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


                      
        瀏覽 51
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            freesexvideos高潮hd护士 | 自拍偷拍小电影 | 成全成人永久免费视频 | 日韩性爱影院 | 美女馒头逼 | 18国产精品 | 国产婬乱a一级毛片视频 | 91精品成人 | 日本一二区不卡视频 | 久久久青青 |