深入理解redux中間件
redux middleware 是 redux 的一個 advanced feature. 這個概念并不是很新奇,以為在 Koa 里面早已經實現過了. 對比與原生的redux middleware , koa 的 middleware 差不多相當于是爸爸級的 level 了. 這么說,是有依據的. 我們這里,來深入一下源碼,具體看一下redux middleware 到底做了些啥.
我們首先來探討一下基本的源碼吧.
redux 的源碼可以說是比較簡單的。 首先,入口文件是 index.js 。我們來看一下,里面具體的內容:
// 從不同的文件里面導出相關方法
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}
這里,其實是 redux 的提供的所有方法了. createStore,combineReducers,bindActionCreators 這三個方法,與 middleware 關系不大,我這里就不贅述了. 這里,主要講解一下 applyMiddleware 方法和 compose 方法.
in fact, compose 是一個非常基礎的方法, 用來以函數式的編程來組合中間件, 在 koa 中我們也同樣遇見過這樣的寫法. applyMiddleware 也是用到這樣的方法的. so, 我們來具體看看.
compose 方法
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];
// 獲取除最后一個以外的函數[0,length-1)
const rest = funcs.slice(0, -1)
// 通過函數 curry 化
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
第一眼看的時候, 肯定超級S B。。。 md... 這寫個啥... 看了一下官方給的注釋就是:
// 簡單 demo 就是
compose(f, g, h) is identical to doing (...args) => f(g(h(...args))).
合著就是個函數嵌套的寫法。。。
關鍵坑爹的在于他的reduceRight方法, 寫的真心6. 由于,return 兩個函數時,只會返回第二個執行的結果:
function test(a,b){
return a(),b();
}
console.log(test(a=>1,b=>2));
// 開玩笑的. 上面那種只是科普一下. 他真正的機制實際上是利用 reduceRight 的第二個參數來執行的
Array.reduceRight(fn,start);
// 主要還是這里的start, 相當于就是 last(...args)
// 將上面代碼翻譯一下就是
rest.reduceRight(function(composed, f){return f(composed)}, last(...args));
//... 慢慢去體會吧...
所以, 一開始看的時候,在糾結 最后一個 composed 都沒執行... 后來發現, 原來還有一層 last(...args).
不過實話說, 真心沒有 koa 里面的 compose 函數寫得好, 你直接先寫一個 noop 函數不行嗎!!!
// 俺 實際寫了一個替換的compose. 感覺這個看的清楚一點
function compose(...funcs) {
return function(...args) {
var len = funcs.length,
middle = funcs[--len](...args);
while (len--) {
middle = funcs[len].call(this, middle);
}
return middle;
}
}
// 測試
console.log(compose(a => a, b => b, (c,b) => c+b)('a', 'b'));
這個是簡單的compose 函數. 下面,我們來看一下重點,關于 redux-middleware 的核心方法, applyMiddleware.
applyMiddleware 中間件
由于這個中間件有點復雜, 對傳入的函數有具體的要求. 我們先來看一下使用該方法的上下文:
直接看 offical website 找到一個 demo:
let store = createStore(
todoApp,
// applyMiddleware() tells createStore() how to handle middleware
applyMiddleware(logger, crashReporter)
)
最終 applyMiddleware return 的結果,還需要返回到 createStore 里去的. 通過 createStore 傳入方法時, 函數里面并未對 里面做什么處理.
function createStore(reducer, preloadedState, enhancer) {
// 這里就是一些列條件判斷, 如果你使用 middle 是上面的形式,那么就會直接將參數賦給 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.')
}
// 實際上調用 applyMiddleware 方法的地方. 注意他傳入的參數即可. z
return enhancer(createStore)(reducer, preloadedState)
}
}
ok, 我們了解了傳入 applyMiddleware 的參數后, 繼續看. 中間件的寫法:
// 這里就看一下logger 就Ok
const logger = store => next => action => {
// debug info
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
// debug info
console.log('next state', store.getState())
console.groupEnd(action.type)
return result
}
// 我們將 debug 信息去掉之后,可以得到一個精簡版的 middleware
const logger = store => next => action => {
// 傳遞前, 執行的代碼
let result = next(action)
// 傳遞完, 執行的代碼
return result
}
看到這里,有種 koa 的感覺. next(action) 顯示的將控制權交給下一個函數進行執行. 相當于就是 onion model.
這里, 放一下 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
}
}
}
看到這里, 我也是有點驚訝, 第一是他的中間件傳遞的復雜度巧妙的轉化為箭頭函數的寫法, 第二是他顯示的改變了真正的dispatch的內容。
最后實際調用整個流程,是直接根據applyMiddleware提供的方法來的:
// 注意這里是 applyMiddleware 提供的 dispatch 方法
store.dispatch(action)
如果按照上面的調用方式寫的話,具體調用順序就是:
applyMiddleware(logger, crashReporter)
applyMiddleware all procedure
applyMiddleware 整個執行過程:
對應于上文, 整個API的流程圖為:
關鍵點在于applyMiddleware 和 中間件兩個內容.
關于 redux-middleware 還有一個比較流行的庫, 即, redux-thunk . 該庫灰常簡單, 就一個函數.
redux-thunk
直接看源碼算了:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
他和原來的中間件的寫法有一個非常不同的地方,在于. 他寫中間件的地方, 不在 createStore 里面, 而在 dispatch 里面.
// 初始化調用
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
// thunk 類型的中間件
function doSth(forPerson) {
// 這里必須返回一個函數... 才能達到中間件的效果
return function (dispatch) {
return async().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
// 更簡便的寫法可以為:
let doSth = forPerson=>dispatch=> async().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
)
不過,怎樣用, 還是看自己吧. 本人比較傾向于使用 原來的 applyMiddleware. 因為, 那個已經滿足我的需求了.
來自:https://segmentfault.com/a/1190000006876228