淺談Redux(之一):Middleware原理

Redux做爲目前最火的Flux模式實現之一,它有不少的點值得研究。今天咱們首先來看看它的Middleware。express

熟悉Express或者koa的朋友對Middleware的概念必定不陌生。例如Express中是這樣使用一箇中間件的:編程

var app = express();

app.use(function(req, res, next) {
  console.log('%s %s', req.method, req.url);
  next();
});

app.use中的方法,能夠在其後面的http VERB調用以前,對request對象和response對象進行處理,而後經過調用next方法將處理過程轉發到下一中間件或者經過返回響應來結束處理過程。(以後有機會的話再寫一寫NodeExpress)。redux

我理解的所謂中間件其實就是,經過相似裝飾者模式的形式,用代碼預處理的方式,保證本來處理問題的函數(或方法)調用不變。promise

Redux中的中間件可使得在用戶調用store.dispatch以後,先對參數stateactions進行預處理,再讓真正的store.dispatch調用,以確保reducer純度(函數式編程的概念)不變。app

Redux中提供了applyMiddleware方法,它的源碼只有十幾行,真的是很是精妙。框架

下面咱們就研究一下它的源代碼。koa

<!--more-->異步

applyMiddleware方法

applyMiddleware(...middlewares){
    
    return next => (reducer, initialState){
       
        var store = next(reducer, initialState),
            dispatch = store.dispatch,
            chain = [],
            middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action)
            };
            
            chain = middlewares.map(middleware =>
                middleware(middlewareAPI));
            
            dispatch = compose(...chain, store.dispatch);
            
            return {
                ...store,
                dispatch
            }
    }
}

這段代碼的意思就是,appleMiddleware方法接收一個Middleware列表,以applyMiddleware(middleware1, middleware2, middleware3)的形式調用(參見ES6的rest參數語法),而後再將建立store的方法傳入(我想這個緣由是Redux不單單能夠在React中使用,也能夠適用於任何Flux模式的框架和庫),而後就會發生神奇的事情。函數式編程

這兩次調用(假設:var newCreateSore = applyMiddleware(middleware1, middleware2)(createStore))會產生一個新的建立Store的方法,可是它改造了本來Store的dispatch方法,讓這個dispatch能夠作原生dispatch不能作的事情,這樣咱們就能夠訂製dispatch的行爲,從而實現了中間件的概念。函數

故而,newCreateStore將做爲createStore的替代方法,使用newCreateStore會產生帶有中間件的store。

在最內層是如何實現中間件的調用的呢?讓咱們繼續研究。

首先咱們用傳入的next(一個能夠建立Store的函數),建立一個原始的store,而且取出其原生的store.dispatch方法和store.getState方法成爲一個對象,做爲參數傳入中間件函數中,讓其第一次包裝這個相似store的對象,並返回新的函數。

而後咱們使用compose函數,將這些包裝事後的返回的函數一個接一個的嵌套調用。

這裏補充一下compose的概念:

假設有若干函數f1, f2, f3...,compose指的是相似f1(f2(f3(x)))的調用方式,這在函數式編程中很常見。

(這裏的compose函數是redux中的一個方法,這裏咱們不上它的源碼,有興趣的朋友能夠直接看源碼。)

被嵌套在compose最內層的是原生的store.dispatch方法,這裏咱們就一層層的將其包裝,在中間件函數中,咱們能夠利用store的其餘方法,好比store.dispatchstore.getState,作一些有意思的事情,好比實現一個記錄state改變的日誌中間件。

中間件函數

從上面的分析中,咱們不難寫一個符合要求的中間件函數。

首先中間件函數須要接受一個middlewareAPI,若是使用ES6的語法,這裏能夠當作是接收一個{dispatch, getState}的形式的參數,這樣咱們就能在內層使用這兩個方法。

接收middlewareAPI參數以後,中間件函數返回另外一個函數(爲方便後面解釋,假設返回的函數爲dispatch n)。這個函數既然要用於compose,也就是說它接收一個形式爲dispatch的函數,對其一層層嵌套(形式爲dispatch1(dispatch2(dispatch3(dispatch))))。在其內部咱們能夠在以前的dispatch調用以前和以後,進行一些邏輯的處理。

寫一個簡單的記錄state日誌的中間件以下:

var middlewareLogger = ({getState}) => next => action => {
    console.log(getState());
    next(action);
    console.log(getState());
}

怎麼樣,是否是特別簡單?

再寫一個異步操做的中間件:

const readyStatePromise = store => next => action => {
  if (!action.promise) {
    return next(action)
  }

  function makeAction(ready, data) {
    let newAction = Object.assign({}, action, { ready }, data)
    delete newAction.promise
    return newAction
  }

  next(makeAction(false))
  return action.promise.then(
    result => next(makeAction(true, { result })),
    error => next(makeAction(true, { error }))
  )
}

這個中間件讓你能夠發起帶有一個 { promise } 屬性的特殊 action。這個 middleware 會在開始時發起一個 action,並在這個 promise resolve 時發起另外一個成功(或失敗)的 action。爲了方便起見,dispatch 會返回這個 promise 讓調用者能夠等待。

結束

相關文章
相關標籤/搜索