在上一週的學習中,咱們熟悉瞭如何經過redux去管理數據,而在這一節中,咱們將一塊兒深刻到redux的知識中學習。html
咱們知道在一個簡單的數據流場景中,點擊一個button後,在回調中分發一個action,reducer收到action後就會更新state並通知view從新渲染,以下圖所示
ios
可是若是須要打印每個action來調試,就得去改dispatch或者reducer實現,使其具有打印功能,那麼該如何作?所以,須要中間件的加入。
編程
上圖展現了應用middleware後的Redux處理事件的邏輯,每一個middleware均可以處理一個相對獨立的事物,經過串聯不一樣的middleware實現變化多樣的功能!json
Redux提供了一個applyMiddleware方法來加載middleware,它的源碼是這樣的:redux
import compose from './compose'; export default function applyMiddleware(...middlewares) { return (next) => (reducer, initalState) => { let store = next(reducer, initalState); let dispatch = store.dispatch; let chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; chain = middlewares.map( middleware => middleware(middlewareAPI)); dispatch = compose(...chain)(store.dispatch); return { ...store, dispatch }; } }
而後咱們再上一個logger middleware的源碼實現:數組
export default store => next => action => { console.log('dispatch:', action); next(action); console.log('finish:', action); }
雖然看到「源碼」的那兩個字的時候,心裏一萬隻草什麼馬奔過,可是一看到代碼這麼精簡,這麼優美,那就初讀一下源碼把。
而後
閉包
接下來就開始解讀上面源碼app
middleware是一個層層包裹的匿名函數,這實際上是函數式編程的currying(Currying就是把一個帶有多個參數的函數拆分紅一系列帶部分參數的函數)。那麼applyMiddleware會對logger這個middleware進行層層的調用,動態的將store和next參數賦值。異步
那麼currying的middleware結構有什麼好處呢?函數式編程
而且applyMiddleware的結構也是一個多層currying的函數,藉助compose,applyMiddleware能夠用來和其餘插件增強createStore函數
經過以下方式建立一個普通的store
let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);
上述代碼執行完後,applyMiddleware方法陸續得到了3個參數,第一個是middlewares數組[mid1, mid2, mid3,...],第二個是Redux原生的createStore方法,最後一個是reducer。而後咱們能夠看到applyMiddleware利用createStore和reducer建立了一個store。而store的getState方法和dispatch方法又分別被直接和間接地賦值給middlewareAPI變量的store
const middleAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middle => middleware(middlewareAPI))
而後,每一個middleware帶着middlewareAPI這個參數分別執行一遍,執行後,獲得一個chain數組[f1, f2, ..., fx, ..., fn],它保存的對象是第二個箭頭函數返回的匿名函數。由於是閉包,每一個匿名函數多能夠訪問相同的store,即middlewareAPI.
這一層只有一行代碼,確是applyMiddleware精華所在。
dispatch = compose(...chain)(store.dispatch);
其中,compose是函數式編程中的組合,它將chain中的全部匿名函數[f1, f2, ..., fn]組裝成一個新的函數,即新的dispatch。當新的dispatch執行的時候,[f1, f2, ...]會從右到左依次執行。Redux中compose的實現是這樣的,固然實現的方式不惟一。
function compose(...funs) { return arg => funcs.reduceRight( (compose, f) => f(composed), arg) }
compose(...funcs)返回的是一個匿名函數,其中funcs就是chain數組。當調用reduceRight時,依次從funcs數組的右端取一個函數f(x)拿來執行,f(x)的參數composed就是前一次f(x+1)執行的結果,而第一次執行的f(n)n表明chain的長度,它的參數arg就是store.dispatch。
所以,當compose執行完後,咱們獲得的dispatch是這樣的:
假設n=3:
dispatch = f1(f2(f3(store.dispatch)));
這時調用dispatch,每個middleware就會依次執行了。
通過compose後,全部的middleware就算是已經串聯起來了。
那麼問題來了?
在分發store時,咱們有說到每一個middleware均可以訪問store,也就是咱們說的經過middlewareAPI這個變量去拿到dispatch屬性進行操做。那麼若是在middleware中調用store.dispatch會如何?和調用next()有什麼區別?
先上一波代碼:
//next() const logger = store => next => action => { console.log('dispatch:', action); next(action); console.log('finish:', action ); } //store.dispatch(action); const logger = store => next => action { console.log('dispatch:', action); store.dispatch(action); console.log('finishL:', action); }
在分發store的時候,咱們有說過:midddleware中的store的dispatch經過匿名函數的方式和最終compose結束後的新dispatch保持一致,因此,在middleware中調用store.dispatch()和在其餘任何地方調用其實效果是同樣的。
而若是在middleware中調用next(),效果是進入下一個middleware中。
具體以下兩個圖1和圖2所示:
如圖1所示,正常狀況下,當咱們去分發一個action時,middleware會經過next(action)一層層處理和傳遞action直到redux原生的dispatch。若是某個middleware中使用了store.dispatch(action)來分發action,就會發生如圖2所示的狀況。這就至關因而又從頭開始了。
那麼問題又來了,假如這個middleware一直簡單粗暴地調用store.dispatch(action),就會造成一個無限循環了,那麼store.dispatch(action)的用武之地到底在哪裏呢?
假如咱們須要發送一個異步請求到服務端獲取數據,成功後彈出個message,這裏咱們一般會用到reduce-thunk這個插件。
const thunk = store => next => action => typeof action === 'function' ? action(store.dispatch, store.getState) : next(action)
代碼很清晰,就是會先判斷你dispatch過來的action是否是一個function,若是是則執行action,若是不是則傳遞到下一個middleware。所以,咱們能夠這樣設計action:
const getMessage = (dispatch, getState) => { const url = 'http://xxx.json'; Axios.get(url) .then( res => { dispatch({ type: 'SHOW_MESSAGE', message: res.data.data }) }) .catch( err => { dispatch({ type: 'ERR_GET', message: 'error' }) }) }
如上所示,只要在應用中去調用store.dispatch(getThenShow), redux-thunk這個middleware接收到後就會去執行getMessage方法。getMessage會先請求數據,根據請求結果去作相對應的分發action。這這裏的dispatch就是經過redux-thunk這個middleware傳遞進來的。 總結:在middleware中使用dispatch的場景通常是接受到一個定向action,而這個action又並不但願到達原生的分發action,每每用在異步請求的需求裏面。
----------------做者的話:其實看了挺多遍,在腦海中構建整個middleware流程,再結合上週學習Redux時的Demo才漸漸的知其形,還須要多在實踐中會其神!--------------