Redux 學習之 middleware詳解

什麼是middleware

middleware翻譯過來叫中間件,這是一個很抽象的名詞,按個人理解的話:「中間」是個形容詞,「件」應該是一個名詞。那麼咱們重點關注中間這個詞,中間,是在上面中間呢?其實就是在你執行正常業務的代碼中間插入一部分其它代碼,具體能夠是在正常代碼的執行前,也能夠在正常代碼執行後。其實學過Spring的童鞋應該很好理解,這個東西跟Spring的切面編程很相似。。。編程

有啥用

前面說了,這個技術可讓咱們在正常的業務代碼先後執行一部分其它代碼,這個其它代碼能夠包括:日誌、鑑權啊等到一些公共處理代碼。簡單來講,只有你想不到,沒有作不到。redux

怎麼用

話很少說,先上代碼:數組

const logger1 =store=> next=> action => {
  console.log('dispatching  logger', action)
  next(action)
  console.log('next state  logger', action)
}

const logger2 =store=> next=> action => {
  console.log('dispatching  logger', action)
  next(action)
  console.log('next state  logger', action)
}

const store = createStore(
  reducer,
  applyMiddleware([logger1,logger2])
)

咱們看到上面的代碼中,首先聲明logger一、logger2兩個middleware(沒錯,這兩個看起來很奇怪的變量就是middleware了。。。),而後在建立store的時候經過applyMiddleware來綁定到dispatch上去,這樣每次咱們分發(dispatch)一個action的時候,兩個middleware裏的代碼都會執行。閉包

對,就是這麼簡單,表面的簡單,背後是大量邏輯很複雜的代碼。。。app

源碼解析

下面先上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)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

這個函數是redux在createStore函數中調用的,因此它返回一個匿名函數,咱們只須要關心內部匿名函數的實現就行了。優化

讓咱們來一步步分析短短的幾行代碼:this

變量聲明

咱們先看applyMiddleware內部匿名函數的前幾行代碼:spa

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

第一行代碼調用createStore函數建立一個store對象,這個沒什麼特別的,過。。。
第二行聲明一個dispatch變量,指向一個箭頭函數,函數直接報錯,用來告訴用戶我這會兒正初始化呢,你敢dispatch我就報錯給你看!
第三行代碼聲明一個middlewareAPI的對象,裏面包含兩個屬性:getState和dispatch。翻譯

getState沒什麼好說的,重點是這個dispatch屬性,這個屬性指向一個箭頭函數,函數直接執行dispatch函數(這個dispatch函數可不是store原生的dispatch,而是咱們在第二行聲明的dispatch變量指向的箭頭函數。

這塊相對來講比較簡單,可是爲了後面咱們好理解,這裏來對前面的變量聲明作以下優化:

const store = createStore(...args) // 不變
    let temDispatch = () => {  // 爲了跟store.dispatch 區分,咱們把變量名稱修改成temDispatch;
      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,
      APIDispatch: (...args) => temDispatch(...args) // 一樣的,爲了區分,咱們這裏用APIDispatch來表示屬性變量
    }

如上代碼所示,爲了同store.dispatch方法區分,咱們分別用 temDispatch和 APIDispatch這兩個名稱來替代原來的dispatch。

分拆Middleware 函數

接下來咱們看下一行代碼:

const chain = middlewares.map(middleware => middleware(middlewareAPI))

middlewares咱們知道是一個包含中間件的數組,經過數組的map處理後,咱們將會"執行一次"中間件函數,而後將返回值放到chain的數組中。

上面咱們說"執行一次「中間件函數,其實說法有點不太好理解,接下來咱們慢慢分析中間件:

const logger1 =store=> next=> action => {
  console.log('dispatching  logger', action)
  next(action)
  console.log('next state  logger', action)
}

上面是一個最簡單的中間件形式,可是仍是有點複雜,咱們能夠先把這個中間件拆分紅如下的樣子:

const inner = action => {
  console.log('dispatching 333 logger', action)
  next(action)
  console.log('next state 4444 logger', action)
};

const middle = next => action => {
  console.log('dispatching 333 logger', action)
  next(action)
  console.log('next state 4444 logger', action)
};

const logger1 = store => next => action => {
  console.log('dispatching  logger', action)
  next(action)
  console.log('next state  logger', action)
}

如上所示,咱們的middleware實際上是一個箭頭函數,不嚴謹的說,這個函數能夠被logger1()()()這樣被調用,由於第一次和第二次被調用都返回一個新的箭頭函數,這裏爲了好理解,咱們把他們拆分爲middle 和inner函數(通常是不能這麼寫的,由於內部的箭頭函數還要經過閉包獲取外部的變量值)。

說了這麼多,其實最終能夠歸結爲一句話,那就是咱們的chain數組裏放的都是middle函數,也就是chain是一個middle函數的集合,這點很重要,咱們後面還會說到這個。

鏈式調用

咱們繼續看下一行代碼:

dispatch = compose(...chain)(store.dispatch)
等價於
temDispatch = compose(...chain)(store.dispatch)

這行代碼看着很簡短,其實很難理解,咱們一步步來看。

轉換鏈式函數

咱們首先來看 compose(...chain)這行代碼。如下是compose代碼的實現:

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

關鍵代碼funcs.reduce((a, b) => (...args) => a(b(...args))), 代入compose(...chain),chain咱們上面說到,是一個middle函數的數組,而後通過reduce處理,這裏比較麻煩,咱們一點點來解釋:

(a, b) => (...args) => a(b(...args))

上面就是reduce函數的回調,

(...args) => a(b(...args) 這是回調的返回值,也是一個箭頭函數,咱們把它命名爲reduceMiddleFunc;

a變量爲上次回調的返回值(不出意外的話,就是一個箭頭函數,要麼是chain數組的第一個值,也就是一個middle函數,要麼就是上次回調的返回值,就是一個reduceMiddleFunc函數 ),

b變量爲當前循環的值,也就是一個middle函數。

這樣可能不太好理解,舉個例子吧,假如說原來的chain數組的值爲[middle1,middle2,middle3,middle4]。那麼compose(...chain)以後,咱們獲得(...args) => middle1(middle2(middle3(middle4(...args))))這樣一個箭頭函數。咱們把它命名爲 chainFunc.

執行鏈式函數

原來的代碼是:

dispatch = compose(...chain)(store.dispatch)
等價於
temDispatch = compose(...chain)(store.dispatch)

通過咱們上面的分析後,咱們獲得如下代碼:

const chainFunc = (...args) => middle1(middle2(middle3(middle4(...args))));
temDispatch = chainFunc(store.dispatch)

接下來咱們來看 chainFunc(store.dispatch),也就是咱們要執行這個鏈式函數了,以下:

const chainFunc = (...args) => middle1(middle2(middle3(middle4(...args))));
temDispatch = chainFunc(store.dispatch) 
// 至關於下面一行
temDispatch = middle1(middle2(middle3(middle4(store.dispatch))))

【注意】:此處的store.dispatch是調用createStore建立的元素store的 dispatch方法,後面咱們會覆蓋原生的dispatch,因此這裏須要注意下。

返回store對象

咱們來看applyMiddleware的最後一行代碼,

return {
      ...store,
      dispatch// 也就是temDispatch
    }

這個實際上是createStore函數的返回值,也就是說咱們上面定義的temDispatch會覆蓋掉初始的store中dispatch。

也就是說,當你調用調用store.dispatch(action)的時候,就至關因而調用 middle1(middle2(middle3(middle4(store.dispatch))))(action),只要最內部的store.dispatch纔是調用真正的dispatch方法。

咱們來簡化一下這個代碼:

const param = middle2(middle3(middle4(store.dispatch)));
store.dispatch(action)
等價於
middle1(param)(action)

還記得middle函數嗎?

const middle = next => action => {
  console.log('dispatching 333 logger', action)
  next(action)
  console.log('next state 4444 logger', action)
};

當咱們執行middle1的時候,就會把param當作next參數來執行,而後返回一個 inner函數:

這是inner函數:

const inner = action => {
  console.log('dispatching 333 logger', action)
  next(action)
  console.log('next state 4444 logger', action)
};

那麼上面的代碼就能夠修改成以下:

const param = middle2(middle3(middle4(store.dispatch)));
store.dispatch(action)
等價於
middle1(param)(action)
等價於
inner(action)

那麼在inner函數內執行next函數,其實就是執行middle2(middle3(middle4(store.dispatch)))這一套,依次類推,就比如是洋蔥同樣,一直執行到最內部真正的 store.dispatch方法爲止。

one more thing

上面我說到,在最後咱們用temDispatch這個函數覆蓋了原始的 store.dispatch函數,那若是咱們是inner中經過 store.dispatch去調用會發什麼狀況呢?

咱們已經說過,applyMiddleware最終會覆蓋原始store上的dispatch方法,改爲咱們的鏈式調用函數,若是在inner裏調用store.dispatch,其實就至關於從新從鏈式函數的最外層的開始調用,這就進死循環了。。。

調用洋蔥圖

相關文章
相關標籤/搜索