- 前言
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大講堂:
- 為什么會出現中間件?
我們知道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)
- 中間件的串聯融合。
中間件的功能各不相同,它們都要融入到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),但是正常的流程是如此。
未完待續……