前言html
這幾天看了redux middleware的運用與實現原理,寫了一個百度搜索的demo,實現了相似redux-thunk和redux-logger中間件的功能。react
項目地址:https://github.com/CanFoo/react-baidu-search/tree/mastergit
redux中間件是經過函數式編程實現,所以要閱讀源碼須要有必定函數式編程基礎,好比柯里化函數的實現,不然難以理解源碼的原因。接下去經過這個demo給你們講解我的對中間件的理解,若有問題,come on 指正。github
redux middleware是什麼編程
若是沒有中間件的運用,redux 的工做流程是這樣 action -> reducer,這是至關於同步操做,由dispatch 觸發action後,直接去reducer執行相應的動做。可是在某些比較複雜的業務邏輯中,這種同步的實現方式並不能很好的解決咱們的問題。好比咱們有一個這樣的需求,點擊按鈕 -> 獲取服務器數據 -> 渲染視圖,由於獲取服務器數據是須要異步實現,因此這時候我就須要引入中間件改變redux同步執行的流程,造成異步流程來實現咱們所要的邏輯,有了中間件,redux 的工做流程就變成這樣 action -> middlewares -> reducer,點擊按鈕就至關於dispatch 觸發action,接下去獲取服務器數據 middlewares 的執行,當 middlewares 成功獲取到服務器就去觸發reducer對應的動做,更新須要渲染視圖的數據。中間件的機制可讓咱們改變數據流,實現如異步 action ,action 過濾,日誌輸出,異常報告等功能。json
如何自定義中間件redux
redux 提供了一個叫 applyMiddleware 的方法,能夠應用多箇中間件,這樣就能夠當觸發action時候就會被這些中間件給捕獲,這邊咱們分別定義了搜索和日誌的中間件,並且中間件傳入順序是先搜索中間件再日誌打印中間件,這個順序是有講究的,下文會說明的數組
import { createStore } from 'redux' import applyMiddleware from './applyMiddleware/applyMiddleware' import compose from './applyMiddleware/compose' import reducer from './reducers' import loggerMiddleware from './middlewares/loggerMiddleware' import searchMiddleware from './middlewares/searchMiddleware' const createStoreWithMiddleware = compose( applyMiddleware( searchMiddleware, loggerMiddleware ), window.devToolsExtension ? window.devToolsExtension() : f => f )(createStore)
searchMiddleware是搜索中間件,其實它就是仿照redux-thunk的實現服務器
export default function thunkMiddleware({ dispatch, getState }) { return next => action => { /*若是action是一個函數,則先執行action,不然經過next進入到下一個action對應的reducer*/ typeof action === 'function' ? action(dispatch, getState) : //這裏action(dispatch, getState)指的是getThenShow返回函數的執行 next(action); }}
咱們先來分析下中間件的執行順序,下文再來解釋爲何這裏結構是() => next => action => {}以及源碼是如何實現中間件鏈。這裏的代碼很是簡單,就是一個三目運算符,當action是一個函數類型,就直接執行這個action函數,不然就經過next來連接到下一個組件,next是專門用來串聯組件間的執行順序。咱們知道,若是沒有中間件的處理,action只能返回一個對象格式,不然reducer不能進行處理action傳過來的行爲,可是有了中間件,咱們就能夠「肆意妄爲」,只要保證最終傳給reducer的action是一個對象,期間從觸發action到真正到達reducer我想要action是什麼就能夠是什麼,下面是搜索邏輯所對應的action閉包
export function getThenShow(value) { return dispatch => { const url = 'https://gsp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su'; jsonp(url, {wd: value}, 'cb', (data) => { dispatch({ type: SHOW_MESSAGE_SUCESS, lists: data.s }); }) } }
當執行dispatch(getThenShow())時,上文所說的searchMiddleware就會捕獲到這個action,由於第一次傳過來的action是
functon(dispatch) { const url = 'https://gsp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su'; jsonp(url, {wd: value}, 'cb', (data) => { dispatch({ type: SHOW_MESSAGE_SUCESS, lists: data.s }); })
如上所示,由於第一次傳過來的action是function類型的,因此searchMiddleware裏就會先執行這個action函數,這個aciton執行jsonp方法回調另外一個dispatch,從上面代碼能夠看出這個時候dispatch分發的action是一個對象,type爲SHOW_MESSAGE_SUCCESS,lists爲搜索結果,所以下一次searchMiddleware捕獲到的action爲一個對象,從而去執行next(action),也就是去執行下一個中間件loggerMiddleware的內容
export default function createLogger({ getState }) { return (next) => (action) => { const prevState = getState(); const returnValue = next(action); const nextState = getState(); console.log(`%c prev state`, `color: #9E9E9E`, prevState); console.log(`%c action`, `color: #03A9F4`, action); console.log(`%c next state`, `color: #4CAF50`, nextState); console.log('===============') return returnValue; }; }
loggerMiddleware裏先經過getState()獲取到當前狀態,接着用經過next(action)執行下一個中間件,因爲applyMiddleware傳入中間件的順序是先searchMiddleware再loggerMiddleware,並且loggerMiddleware以後沒有其它中間件傳入,所以此時的next指的是原生的dispatch,進而會去觸發reducer所對應的動做,因此再次調用getSatate()會返回下一個狀態內容。
因此當在搜索框輸入內容redux執行的步驟爲:
1.輸入內容觸發dispatch(getThenShow())發起第一個action(這裏getThenShow()返回的action的是一個函數)
2.searchMiddleware捕獲到第一個action,由於action返回是一個函數,因此執行action
3.第一個action執行完後回調再次觸發dispatch({type: SHOW_MESSAGE_SUCCESS, lists: ...})發起第二個action
4.searchMiddleware捕獲到第二個action,執行next(action),連接到loggerMiddleware
5.loggerMiddleware獲取當前(prev)狀態後執行next(action)從而觸發reducer的對應的動做
6.loggerMiddleware再次獲取當前(next)狀態,而後打印出狀態,執行完loggerMiddleware程序
7.回溯到searchMiddleware,searchMiddleware程序執行完
8.整個redux中間件執行完
到這裏,咱們就把這個demo的中間件執行順序分析完,那麼爲何自定義中間件是() => next => action => {}這樣的結構?next又是如何將中間件串聯起來的?其實這個兩個問題是同一個問題,由於中間件設定這樣() => next => action => {}的結構目的就是爲了把中間件串聯起來的。爲了一探究竟,咱們仍是得來來看看applyMiddleware源碼的實現
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
從applyMiddleware執行完後最終返回的也是一個閉包函數,將建立 store的步驟放在這個閉包內執行,這樣中間件就能夠共享 store 對象。applyMiddleware是這樣來對傳進來中間件進行函數式編程處理的
1.經過...middlewares將全部的中間件存入到middlewares數組中
2.middlewares 數組經過 map 方法執行生成新的 middlewares 數組且每一項都傳入middlewareAPI,傳入middlewareAPI的目的就使得每一箇中間件均可以訪問到store,這時候middlewares數組的每一項都變爲了
function (next) { return function (action) {...} }
3.compose 方法將新的 middlewares 和 store.dispatch 結合起來,生成一個新的 dispatch 方法。這裏的關鍵點是compose,咱們來看看compose的設計
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) const fn = (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) return fn }
能夠看到 compose 方法實際上就是利用Array.prototype.reduceRight來進行下一步處理的。若是對reduceRight方法不瞭解的童鞋,能夠先看看這裏。咱們這裏能夠來模擬一下compose函數處理完的結果,假設咱們這邊有兩個中間件A和B,則傳入到compose的func爲[A, B],且A、B的形式已是(next) => (action) => {}
function A(next) { console.log('A...next === ', next) return function(action) { console.log('A...action') next(action) } } function B(next) { console.log('B...next === ', next) return function(action) { console.log('B...action') next(action) } } function compose(funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) const fn = (args) => rest.reduceRight((composed, f) => f(composed), last(args)) return fn } var fnArr = [A, B] var dispatch = compose(fnArr)("store.dispatch") console.log('new dispatch === ', dispatch)
執行的結果是
由結果能夠看到中間件A的next是指向中間件B的最內層閉包函數,而中間件B的next則是指向原生的dispatch,因此經過compose執行完後,全部的中間件就經過next串聯起來了。這也就是爲何咱們所分析這個百度搜索demo中的searchMiddleware的next是指向loggerMiddleware,而loggerMiddleware的next指向原生dispatch的緣由。
4.返回的 store 新增了一個 dispatch 方法, 這個新的 dispatch 方法是改裝過的 dispatch,由上例中這個改裝過的 dispatch就是指的是中間件A最裏層的閉包函數,這也就是爲何說有了中間件就能夠捕獲action的行爲的原理。
到此applyMiddleware源碼分析完畢,咱們也能夠明白爲何自定義組件須要設計成() => next => action => {}的形式,其實也就是設計成柯里化方式,由於這樣方便進行compose,從而達到動態產生 next 方法以及保持 store 的一致性。