Redux學習之解讀applyMiddleware源碼深入middleware工作機制


隨筆前言

上一周的學習中,我們熟悉了如何通過redux去管理數據,而在這一節中,我們將一起深入到redux的知識中學習。

首先談一談為什么要用到middleware

我們知道在一個簡單的數據流場景中,點擊一個button后,在回調中分發一個action,reducer收到action后就會更新state並通知view重新渲染,如下圖所示

但是如果需要打印每一個action來調試,就得去改dispatch或者reducer實現,使其具備打印功能,那么該如何做?因此,需要中間件的加入。

上圖展示了應用middleware后的Redux處理事件的邏輯,每個middleware都可以處理一個相對獨立的事物,通過串聯不同的middleware實現變化多樣的功能!

小結:Redux中的reducer更加的專注於轉化邏輯,所以middleware是為了增強dispatch而出現的。

middleware是如何工作的

Redux提供了一個applyMiddleware方法來加載middleware,它的源碼是這樣的:

import compose from './compose';

export default function applyMiddleware(...middlewares) {
    return (next) => (reducer, initalState) => {
        let store = next(reducer, initalState);
        let dispatch = store.dispatch;
        let chain = [];

        var middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        };
        chain = middlewares.map( middleware => middleware(middlewareAPI));
        dispatch = compose(...chain)(store.dispatch);

        return {
            ...store,
            dispatch
        };

    }

}

然后我們再上一個logger middleware的源碼實現:

export default store => next => action => {
    console.log('dispatch:', action);
    next(action);
    console.log('finish:', action);
}

雖然看到“源碼”的那兩個字的時候,內心一萬只草什么馬奔過,但是一看到代碼這么精簡,這么優美,那就初讀一下源碼把。
然后

接下來就開始解讀上面源碼

深入解析middleware運行原理

1. 函數式編程思想設計

middleware是一個層層包裹的匿名函數,這其實是函數式編程的currying(Currying就是把一個帶有多個參數的函數拆分成一系列帶部分參數的函數)。那么applyMiddleware會對logger這個middleware進行層層的調用,動態的將store和next參數賦值。

那么currying的middleware結構有什么好處呢?

  • 1.1 易串聯: currying函數具有延遲執行的特性,通過不斷currying形成的middleware可以積累參數,再配合組合(compose)的方式,這樣很容易就形成pipeline來處理數據流
  • 1.2 共享store:在applyMiddleware執行的過程當中,store還是舊的,但是因為閉包的存在,applyMiddleware完成之后,所有的middleware內部拿到的store是最新的且是相同的。

並且applyMiddleware的結構也是一個多層currying的函數,借助compose,applyMiddleware可以用來和其他插件加強createStore函數

2. 給middleware分發store

通過如下方式創建一個普通的store

    let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);

上述代碼執行完后,applyMiddleware方法陸續獲得了3個參數,第一個是middlewares數組[mid1, mid2, mid3,...],第二個是Redux原生的createStore方法,最后一個是reducer。然后我們可以看到applyMiddleware利用createStore和reducer創建了一個store。而store的getState方法和dispatch方法又分別被直接和間接地賦值給middlewareAPI變量的store

    const middleAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middle => middleware(middlewareAPI))

然后,每個middleware帶着middlewareAPI這個參數分別執行一遍,執行后,得到一個chain數組[f1, f2, ..., fx, ..., fn],它保存的對象是第二個箭頭函數返回的匿名函數。因為是閉包,每個匿名函數多可以訪問相同的store,即middlewareAPI.

3.組合串聯middleware

這一層只有一行代碼,確是applyMiddleware精華所在。

    dispatch = compose(...chain)(store.dispatch);

其中,compose是函數式編程中的組合,它將chain中的所有匿名函數[f1, f2, ..., fn]組裝成一個新的函數,即新的dispatch。當新的dispatch執行的時候,[f1, f2, ...]會從右到左依次執行。Redux中compose的實現是這樣的,當然實現的方式不唯一。

   function compose(...funs) {
       return arg => funcs.reduceRight( (compose, f) => f(composed), arg)
   }
compose(...funcs)返回的是一個匿名函數,其中funcs就是chain數組。當調用reduceRight時,依次從funcs數組的右端取一個函數f(x)拿來執行,f(x)的參數composed就是前一次f(x+1)執行的結果,而第一次執行的f(n)n代表chain的長度,它的參數arg就是store.dispatch。

因此,當compose執行完后,我們得到的dispatch是這樣的:

假設n=3:
dispatch = f1(f2(f3(store.dispatch)));

這時調用dispatch,每一個middleware就會依次執行了。

4.在middleware中調用dispatch會發生什么呢?

經過compose后,所有的middleware就算是已經串聯起來了。

那么問題來了?

在分發store時,我們有說到每個middleware都可以訪問store,也就是我們說的通過middlewareAPI這個變量去拿到dispatch屬性進行操作。那么如果在middleware中調用store.dispatch會如何?和調用next()有什么區別?

先上一波代碼:

    //next()
    const logger = store => next => action => {
        console.log('dispatch:', action);
        next(action);
        console.log('finish:', action );
    }
    
    //store.dispatch(action);
    const logger = store => next => action {
        console.log('dispatch:', action);
        store.dispatch(action);
        console.log('finishL:', action);
    }

在分發store的時候,我們有說過:midddleware中的store的dispatch通過匿名函數的方式和最終compose結束后的新dispatch保持一致,所以,在middleware中調用store.dispatch()和在其他任何地方調用其實效果是一樣的。

而如果在middleware中調用next(),效果是進入下一個middleware中。

具體如下兩個圖1和圖2所示:

如圖1所示,正常情況下,當我們去分發一個action時,middleware會通過next(action)一層層處理和傳遞action直到redux原生的dispatch。如果某個middleware中使用了store.dispatch(action)來分發action,就會發生如圖2所示的情況。這就相當於是又從頭開始了。

那么問題又來了,假如這個middleware一直簡單粗暴地調用store.dispatch(action),就會形成一個無限循環了,那么store.dispatch(action)的用武之地到底在哪里呢?

假如我們需要發送一個異步請求到服務端獲取數據,成功后彈出個message,這里我們通常會用到reduce-thunk這個插件。

const thunk = store => next => action => 
    typeof action === 'function' ? 
        action(store.dispatch, store.getState) :
        next(action)

代碼很清晰,就是會先判斷你dispatch過來的action是不是一個function,如果是則執行action,如果不是則傳遞到下一個middleware。因此,我們可以這樣設計action:

    const getMessage = (dispatch, getState) => {
        const url = 'http://xxx.json';
        Axios.get(url)
          .then( res => {
              dispatch({
                  type: 'SHOW_MESSAGE',
                  message: res.data.data
              })
          })
          .catch( err => {
              dispatch({
                  type: 'ERR_GET',
                  message: 'error'
              })
          }) 
    }
如上所示,只要在應用中去調用store.dispatch(getThenShow), redux-thunk這個middleware接收到后就會去執行getMessage方法。getMessage會先請求數據,根據請求結果去做相對應的分發action。這這里的dispatch就是通過redux-thunk這個middleware傳遞進來的。

總結:在middleware中使用dispatch的場景一般是接受到一個定向action,而這個action又並不希望到達原生的分發action,往往用在異步請求的需求里面。

----------------作者的話:其實看了挺多遍,在腦海中構建整個middleware流程,再結合上周學習Redux時的Demo才漸漸的知其形,還需要多在實踐中會其神!--------------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM