redux中間件的原理


  • 前言
    react已經出來很久了,其生態圈之龐大,一鍋燉不下!各種react-xx,已讓我們不堪重負,github上隨便一個demo,引入的模塊至少都是五指之數+。看着頭疼,嚼之無味……。
    在此建議新學者,可以從基礎的核心模塊學起,前期不要考慮那些數量繁多的馬仔小弟,邊學邊寫,個人感覺前期核心要學的流程大致如下:
    React ——> React + redux + React-redux ——> React + redux + React-redux + React-router ——> React + redux + React-redux + React-router ;
    其它的,看情況學習和了解,我也很菜,以上感悟都是針對初學者,希望可以減少他們在學習過程中接觸過多的東西,而影響學習信心和樂趣。
  • 文檔
    React小書(作者從無到有,講述了React的起源,通俗易懂)Note: 第三階段的文檔現在開始收費查看了,不過對於搞前端的人來說不用錢也可以來個親密接觸的(大家自己想辦法)
    Redux莞式教程(拋開需求講實用性都是耍流氓,作者扮演一位PM給我們上了生動的一課,深入淺出,簡明扼要)
    React-Router文檔(一部中規中矩的翻譯之作)

以上是整理的一些說明和文檔資料,沒有看過的可以去了解一下。下面將開始本文的主題:redux的中間件applyMiddleware。

都說名字越長,越讓學者害怕,applyMiddleware的名字看起來就挺嚇人,那么為什么會出現中間件,它是做什么的?它為什么叫中間件?為什么說可以用來解決異步dispatch?經過一段時間的了解,讓我漸漸明白了它的工作原理,現在讓我們帶問題,懷着簡單,輕松的心態走進applyMiddleware大講堂:

 

  1. 為什么會出現中間件?
    我們知道redux的核心,就是控制和管理所有的數據輸入輸出,因此有了dispatch,由於dispatch是一個很純的純函數,就是單純的派發action來更改數據,其功能簡單且固定。
    假如現在產品經理A某有個需求,要求記錄每次的dispatch記錄,我們怎么辦呢?最簡單直接的辦法就是在每一個dispatch的前面加上:
    console.log('dispatching', action);
    dispatch(action)

     假如又來一個產品B說,我需要記錄每次數據出錯的原因,我們怎么辦呢?然后我們又需要在對每一個dispatch做修改

    try{
      dispatch(action)    
    }catch(err){
      console.error('錯誤報告: ', err)  
    }  

    如果我們的程序中有很多的dispatch,我們就需要添加很多的重復代碼,雖然編輯器提供批量替換,但這無疑是產生了很多樣板代碼。
    因為所有的需求都是和dispatch息息相關,所以只要我們把日志放進dispatch函數里,不就好了嗎,我們只需要更改dispatch函數,把dispatch進行一層封裝。
    大概的封裝就是下面這樣:

    let next = store.dispatch
    store.dispatch = function dispatchAndLog(action) {
      console.log('dispatching', action)
      next(action)
    }

    Redux把這個封裝的入口寫成了一個函數,就叫applyMiddleware。
    由此我們明白了applyMiddleware的功能:改造dispatch函數,產生真假dispatch,而中間件就是運行在假真(dispatchAndLog和next)之間的代碼。
    這里我們要對applyMiddleware進行一個准確的定義,它只是一個用來加工dispatch的工廠,而要加工什么樣的dispatch出來,則需要我們傳入對應的中間件函數(比如上例中的dispatchAndLog),下面我們構造一個精簡版的applyMiddleware:

    const applyMiddleware = function(middleware){
      let next = store.dispatch;
      store.dispatch = middleware(store)(next);  // 這里傳入store,是因為中間件中有可能會用到getState獲取數據,比如打印當前用戶等需求
    }
    
    applyMiddleware(dispatchAndLog) 

     

  2. 中間件的串聯融合。
    中間件的功能各不相同,它們都要融入到dispatch中,在派發action的時候,按照順序一個個的執行,這是一個費腦經的事情。
    假如現在我們有兩個中間件 logger和collectError兩個中間件函數,那么大概的執行順序就是 dispatch——>logger改造——>collectError改造。這里我們能看到后面的中間件需要接收到前面改造后的dispatch,
    在前面,我們是直接修改store.dispatch,現在我們換一種寫法,讓每一個中間件函數,接收一個dispatch,然后返回一個改造后的dispatch,來作為下一個中間件函數的next,以此類推,用ES6的寫法大概代碼如下:
    const logger = store => next => action => {
      console.log('dispatching', action)
      return next(action)
    }
    
    const collectError = store => next => action => {
      try {
        return next(action)
      } catch (err) {
        console.error('Error!', err)
      }
    }

     然后,我們改造一下applyMiddleware,來接收一個middlewares數組:

    function applyMiddleware(middlewares) {
      middlewares = middlewares.slice()
      middlewares.reverse()
    
      let dispatch = store.dispatch
      middlewares.forEach(middleware =>
        dispatch = middleware(store)(dispatch)
      )
      return Object.assign({}, store, { dispatch })
    }

     

    上面的middleware(store)(dispatch) 就相當於是 const logger = store => next => {},這就是構造后的dispatch,繼續向下傳遞。這里middlewares.reverse(),進行數組反轉的原因,是最后構造的dispatch,實際上是最先執行的。因為在applyMiddleware串聯的時候,每個中間件只是返回一個新的dispatch函數給下一個中間件,實際上這個dispatch並不會執行。只有當我們在程序中通過store.dispatch(action),真正派發的時候,才會執行。而此時的dispatch是最后一個中間件返回的包裝函數。然后依次向前遞推執行。
    我們拿logger和collectError來說明:

    構造過程:

    let next = store.dispatch;
    let dispatch1 = logger(store)(next); 
    // 這時候的console.log('dispatching', action) 是沒有執行的
    
    let dispatch2 = collectError(store)(dispatch1);
    // 這時候的console.log('Error!', err) 也是沒有執行的
    
    store.dispatch = dispatch2;

    執行過程:

    store.dispatch(action); //假如我們程序中派發了某個action
    
    //相當於是下面這樣
    dispatch2(action); //此時執行了 console.log('Error', err)
    
    //由於collectError中間件中的next是接收的logger返回函數即dispatch1,所以在開始執行
    dispatch1(action); //此時執行了 console.log('dispatching', action)
    
    // 這個例子不太合理,因為錯誤報告是先 try 的 next(action),但是正常的流程是如此。

     

     未完待續……

 


免責聲明!

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



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