十分鐘理解Redux中間件

因爲一直用業界封裝好的如 redux-logger、redux-thunk此類的中間件,並無深刻去了解過 redux中間件的實現方式。正好前些時間有個需求須要對 action執行時作一些封裝,因而藉此瞭解了下 Redux Middleware的原理。

* 中間件概念

首先簡單提下什麼是中間件,該部分與下文關係不大,能夠跳過。來看眼這個經典的圖。react

clipboard.png

不難發現:redux

  1. 不使用middleware時,在dispatch(action)時會執行rootReducer,並根據actiontype更新返回相應的state
  2. 而在使用middleware時,簡言之,middleware會將咱們當前的action作相應的處理,隨後再交付rootReducer執行。

簡單實現原理

好比現有一個action以下:segmentfault

function getData() {
  return {
      api: '/cgi/getData',
      type: [GET_DATA, GET_DATA_SUCCESS, GET_DATA_FAIL]
  }
}

咱們但願執行該action時能夠發起相應請求,而且根據請求結果由定義的type匹配到相應的reducer,那麼能夠自定義方法處理該action,所以該方法封裝成中間件以前多是這樣的:api

async function dispatchPre(action, dispatch) {
    const api = action.api;
    const [ fetching_type, success_type,  fail_type] = action.type;
    // 拉取數據
    const res = await request(api);
    
    // 拉取時狀態
    dispatch({type: fetching_type});
    // 成功時狀態
    if (res.success) {
        dispatch({type: success_type, data: res.data});
        console.log('GET_SUCCESS');
    }
    // 失敗時狀態
    if (res.fail) {
        dispatch({type: fail_type});
        console.log('GET_FAIL');
    };
}

// 調用: dispatchPre(action(), dispatch)

那如何封裝成中間件,讓咱們在能夠直接在dispatch(action)時就作到這樣呢?可能會首先想到改變dispatch指向數組

// 儲存原來的dispatch
const dispatch = store.dispatch;
// 改變dispatch指向
store.dispatch = dispatchPre;
// 重命名
const next = dispatch;

截止到這咱們已經瞭解了中間件的基本原理了~閉包

源碼分析

瞭解了基本原理能有助於咱們更快地讀懂middleware的源碼。
業務中,通常咱們會這樣添加中間件並使用。app

createStore(rootReducer, applyMiddleware.apply(null, [...middlewares]))

接下來咱們能夠重點關注這兩個函數createStoreapplyMiddlewareasync

CreateStore

// 摘至createStore
export function createStore(reducer, rootState, enhance) {
    ...
    if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
          throw new Error('Expected the enhancer to be a function.')
        }
    /*
        若使用中間件,這裏 enhancer 即爲 applyMiddleware()
        如有enhance,直接返回一個加強的createStore方法,能夠類比成react的高階函數
    */
    return enhancer(createStore)(reducer, preloadedState)
  }
  ...
}

ApplyMiddleware

再看看applyMiddleware作了什麼,applyMiddleware函數很是簡單,就十來行代碼,這裏將其完整複製出來。函數

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // 一、將store對象的基本方法傳遞給中間件並依次調用中間件
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 二、改變dispatch指向,並將最初的dispatch傳遞給compose
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

執行步驟

根據源碼,咱們能夠將其主要功能按步驟劃分以下:源碼分析

一、依次執行middleware

middleware執行後返回的函數合併到一個chain數組,這裏咱們有必要看看標準middleware的定義格式,以下

export default store => next => action => {}

// 即
function (store) {
    return function(next) {
        return function (action) {
            return {}
        }
    }
}

那麼此時合併的chain結構以下

[    ...,
    function(next) {
        return function (action) {
            return {}
        }
    }
]

二、改變dispatch指向。

想必你也注意到了compose函數,compose函數以下:
[...chain].reduce((a, b) => (...args) => a(b(...args)))
實際就是一個柯里化函數,即將全部的middleware合併成一個middleware,並在最後一個middleware中傳入當前的dispatch

*compose可能會看得有點蒙,不理解柯里化函數的同窗能夠跳到一個例子讀懂compose先了解下。

// 假設chain以下:
chain = [
    a: next => action => { console.log('第1層中間件') return next(action) }
    b: next => action => { console.log('第2層中間件') return next(action) }
    c: next => action => { console.log('根dispatch') return next(action) }
]

調用compose(...chain)(store.dispatch)後返回a(b(c(dispatch)))
能夠發現已經將全部middleware串聯起來了,並同時修改了dispatch的指向。
最後看一下這時候compose執行返回,以下

dispatch = a(b(c(dispatch)))

// 調用dispatch(action)
// 執行循序
/*
   1. 調用 a(b(c(dispatch)))(action) __print__: 第1層中間件
   2. 返回 a: next(action) 即b(c(dispatch))(action)
   3. 調用 b(c(dispatch))(action) __print__: 第2層中間件
   4. 返回 b: next(action) 即c(dispatch)(action)
   5. 調用 c(dispatch)(action) __print__: 根dispatch
   6. 返回 c: next(action) 即dispatch(action)
   7. 調用 dispatch(action)
*/

*一個例子讀懂compose

上文提到compose是個柯里化函數,能夠當作是將全部函數合併成一個函數並返回的函數。
例如先定義3個方法

function A(x){
    return x + 'a'
}

function B(y){
    return y + 'b'
}

function C(){
    return 'c'
}

var d = [...A, b, C].reduce((a, b) => (d) => {console.log(d, a, b); a(b(d))})

d // 打印d

// f (d) { console.log(d, a, b); return a(b(d)) }

d('d') // 調用d

/*
 * d
 * f(d) { console.log(d, a, b); return a(b(d)) }
 * f C() { return 'c' }
*/

/*
 * c
 * f A(x) { return x + 'a' }
 * f B(y) { return y + 'b' }
*/

不難發現,使用閉包,在調用d的時候,將ab函數儲存在了內存中,調用時會依次將數組從右至左的函數返回作爲參數傳遞給下一個函數使用

相關文章
相關標籤/搜索