Redux學習之解讀applyMiddleware源碼深刻middleware工做機制

隨筆前言

上一週的學習中,咱們熟悉瞭如何經過redux去管理數據,而在這一節中,咱們將一塊兒深刻到redux的知識中學習。html

首先談一談爲何要用到middleware

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

可是若是須要打印每個action來調試,就得去改dispatch或者reducer實現,使其具有打印功能,那麼該如何作?所以,須要中間件的加入。
編程

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

小結:Redux中的reducer更加的專一於轉化邏輯,因此middleware是爲了加強dispatch而出現的。

middleware是如何工做的

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

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);
}

雖然看到「源碼」的那兩個字的時候,心裏一萬隻草什麼馬奔過,可是一看到代碼這麼精簡,這麼優美,那就初讀一下源碼把。
而後
閉包

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

深刻解析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才漸漸的知其形,還須要多在實踐中會其神!--------------

相關文章
相關標籤/搜索