要想實現一個 redux 中間件咱們必須瞭解 redux 的基本實現原理。本文將從 redux 源碼入手,重點講解 applyMiddleware 如何將中間件串聯執行。只有理解了底層原理咱們才能夠遊刃有餘的寫出一個 redux 中間件。javascript
redux 經過 createStore 來建立一個 store 對象html
要理解 applyMiddleware 的實現原理,咱們要從 createStore 入手java
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// 篇幅有限,後面被我省略了,有興趣請去看 redux 源碼
// ......
複製代碼
能夠看見 createStore 的三個參數依次爲: reducer, preloadedState, enhancer。參見源碼,若是傳入了 enhance 參數且爲函數,則將 createStore 傳入 enhancereact
return enhancer(createStore)(reducer, preloadedState)
ios
也就是說,如今咱們將用 enhance 來建立一個 store 對象。json
通常狀況下 createStore 的第三個參數 enhance 就是 applyMiddlewaveredux
applyMiddlewave 的代碼只有二十多行倒是本文的重點axios
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
}
}
}
複製代碼
參見 createStore 的源碼能夠得知:applyMiddlewave 依然使用 createStore 建立了store 對象而且返回,只是改寫了這個對象的 dispatch 方法。數組
下面咱們重點來看這個被改寫掉的 dispatch 方法,同時理解它和原生 dispatch 方法的區別也是本文的重點。爲了更直觀的瞭解這個過程咱們先來看一個 簡單的中間件實現 logger middlewave閉包
export default store => next => action => {
const start = Date.now();
next(action);
const ms = Date.now() - start;
console.log(`dispatch: ${action.type} - ${ms}ms`);
}
複製代碼
下面分二步詳細探討中間件的運行原理
將原生的 getState 和 dispacth 做爲第一個參數傳入中間件數組,得到執行完的 chain 數組;
chain = middlewares.map(middleware => middleware(middlewareAPI))
組合串聯 middlewave
dispatch = compose(...chain)(store.dispatch)
compose 將全部的中間件串聯起來組成新的 dispatch
compose 源碼
function compose(...funcs) {
return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}
複製代碼
參考咱們的 logger middlewave 這裏的 composed 便是咱們的 next 參數。
reduceRight 和 ruduce 同樣,不過 reduceRight 是從數組的右端開始執行,arg 將做爲 reduceRight 的初始值(這裏就是 store.dispatch)。假設咱們的 chain 數組爲 [f1,f2,f3]
執行完畢後 dispatch 爲 dispatch = f1(f2(f3(store.dispatch))))
,調用這個新的 dispatch 每一箇中間件就能依次執行了,這裏的中間件執行過程也是相似於 Koa 的中間件是很是經典的洋蔥模型。只有最後一箇中間件會觸發 redux 原生的 dispatch,將這個 action 分發出去。(沒錯,我就是靈魂畫師)
通常而言 dispatch 只能分發一個 action 對象,可是使用了 redux-thunk 中間件咱們卻能夠分發一個異步函數。
const thunk = store => next => action => {
typeof action === 'function' ?
action(store.dispatch,store.getState) :
next(action)
}
複製代碼
一個異步的 action 的示例
function getMessage = (dispatch, getState) => {
axios.get('xxx/xxx')
.then((res) => {
dispatch({
type: 'GET_MESSAGE_SUCCESS',
message: res.json(),
});
})
.catch((err) => {
dispatch({
type: 'GET_MESSAGE_ERROR',
message: 'error'
});
});
}
複製代碼
這裏的 dispatch 任然是改造後的 dispatch 由於傳入中間件的第一個參數 store 即 middlewareApi 中的 dispatch 是一個閉包保存着對最外層函數 dispatch 的引用,因此當 diapatch 被改寫後後面調用的 dispatch 都是這個新的 dispatch(即中間件的串聯),因此即便在異步 action 中分發一個 action 依然會將所有中間件再執行一遍。
因此理解了以上,編寫一箇中間件將超級簡單,只須要按照中間件編寫規範
function myMiddleware = store => next => action = {
// 在這裏你能夠拿到 store.getState 和 store.dispatch
// 注意若是你調用 store.dispatch 中間件又重新從最外層開始 若是不加限制條件將陷入死循環
// do something
next(action) // 進入下一個中間件,最後一箇中間件的 next 參數爲 redux 原生 dispatch
// 返回繼續執行這個中間件剩餘部分
}
複製代碼
深刻理解 redux 中間件的實現原理,可讓咱們在平常工做中,對 redux 數據流向更加清晰和對本身的程序更加有把握。本人水平有限,若有錯誤還請指出。
原創文章,轉載請註明原地址
若是你喜歡的話,可不能夠給我點個當心心(*^__^*) 嘻嘻……