多數
redux
初學者都會使用redux-thunk
這個中間件來處理異步請求(好比我)git
原本寫這篇文章只是想寫寫redux-thunk
,而後發現還不夠,就順便把middleware
給過了一遍。github
thunk
?
thunk
是一種包裹一些稍後執行的表達式的函數。編程
redux-thunk
源碼全部的代碼就只有15行,我說的是真的。。 redux-thunkredux
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;
複製代碼
代碼很精簡,可是功能強大,因此很是有必要去了解一下。bash
上圖描述了一個redux
中簡單的同步數據流動的場景,點擊button
後,dispatch
一個action
,reducer 收到 action 後,更新state
後告訴UI
,幫我從新渲染一下。app
redux-middleware
就是讓咱們在dispatch
action
以後,在action
到達reducer
以前,再作一點微小的工做,好比打印一下日誌什麼的。試想一下,若是不用middleware
要怎麼作,最navie
的方法就是每次在調用store.dispatch(action)
的時候,都console.log
一下action
和next State
。異步
store.dispatch(addTodo('Use Redux'));
複製代碼
naive
的方法,唉,每次都寫上吧const action = addTodo('Use Redux');
console.log('dispatching', action);
store.dispatch(action);
console.log('next state', store.getState());
複製代碼
function dispatchAndLog(store, action) {
console.log('dispatching', action);
store.dispatch(action);
console.log('next state', store.getState());
}
複製代碼
dispatch
的時候都要import
這個函數進來,有點麻煩是否是,那怎麼辦呢?既然dispatch
是逃不走的,那就在這裏動下手腳,redux
的store
就是一個有幾種方法的對象,那咱們就簡單修改一下dispatch
方法。ide
const next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action);
next(action); // 以前是 `dispatch(action)`
console.log('next state', store.getState());
}
複製代碼
這樣一來咱們不管在哪裏dispatch
一個action
,都能實現想要的功能了,這就是中間件的雛形。函數
接下來就是怎麼加入多箇中間件了。ui
function patchStoreToAddLogging(store) {
const next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
function patchStoreToAddCrashReporting(store) {
const next = store.dispatch
store.dispatch = function dispatchAndReportErrors(action) {
try {
return next(action)
} catch (err) {
console.error('Caught an exception!', err)
Raven.captureException(err, {
extra: {
action,
state: store.getState()
}
})
throw err
}
}
}
複製代碼
patchStoreToAddLogging
和patchStoreToAddCrashReporting
對dispatch
進行了重寫,依次調用這個兩個函數以後,就能實現打印日誌和異常處理的功能。
patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)
複製代碼
store.dispatch
。若是直接返回一個新的dispatch
函數呢?function logger(store) {
const next = store.dispatch
// 以前:
// store.dispatch = function dispatchAndLog(action) {
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
複製代碼
這樣寫的話咱們就須要讓store.dispatch
等於這個新返回的函數,再另外寫一個函數,把上面兩個middleware
鏈接起來。
function applyMiddlewareByMonkeypatching(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
// Transform dispatch function with each middleware.
middlewares.forEach(middleware => (store.dispatch = middleware(store)))
}
複製代碼
middleware(store)
會返回一個新的函數,賦值給store.dispatch
,下一個middleware
就能拿到一個的結果。
接下來就能夠這樣使用了,是否是優雅了一些。
applyMiddlewareByMonkeypatching(store, [logger, crashReporter])
複製代碼
咱們爲何還要重寫dispatch
呢?固然啦,由於這樣每一箇中間件均可以訪問或者調用以前封裝過的store.dispatch
,否則下一個middleware
就拿不到最新的dispatch
了。
function logger(store) {
// Must point to the function returned by the previous middleware:
const next = store.dispatch
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
複製代碼
鏈接middleware
是頗有必要的。
可是還有別的辦法,經過柯里化的形式,middleware
把dispatch
做爲一個叫next
的參數傳入,而不是直接從store
裏拿。
function logger(store) {
return function wrapDispatchToAddLogging(next) {
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
}
複製代碼
柯里化就是把接受多個參數的函數編程接受一個單一參數(注意是單一參數)的函數,並返回接受餘下的參數且返回一個新的函數。
舉個例子:
const sum = (a, b, c) => a + b + c;
// Curring
const sum = a => b => c => a + b + c;
複製代碼
用ES6
的箭頭函數,看起來更加舒服。
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
const crashReporter = store => next => action => {
try {
return next(action)
} catch (err) {
console.error('Caught an exception!', err)
Raven.captureException(err, {
extra: {
action,
state: store.getState()
}
})
throw err
}
}
複製代碼
接下來咱們就能夠寫一個applyMiddleware
了。
// 注意:這是簡單的實現
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
let dispatch = store.dispatch
middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)))
return Object.assign({}, store, { dispatch })
}
複製代碼
上面的方法,不用馬上對store.dispatch
賦值,而是賦值給一個變量dispatch
,經過dispatch = middleware(store)(dispatch)
來鏈接。
如今來看下redux
中applyMiddleware
是怎麼實現的?
/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */
// 就是把上一個函數的返回結果做爲下一個函數的參數傳入, compose(f, g, h)和(...args) => f(g(h(...args)))等效
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼
compose
最後返回的也是一個函數,接收一個參數args
。
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 確保每一個`middleware`都能訪問到`getState`和`dispatch`
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// wrapDispatchToAddLogging(store)
dispatch = compose(...chain)(store.dispatch)
// wrapCrashReport(wrapDispatchToAddLogging(store.dispatch))
return {
...store,
dispatch
}
}
}
複製代碼
借用一下大佬的圖, google搜索redux-middleware第一張
到這裏咱們來看一下applyMiddleware
是怎樣在createStore
中實現的。
export default function createStore(reducer, preloadedState, enhancer){
...
}
複製代碼
createStore
接受三個參數:reducer
, initialState
, enhancer
。enhancer
就是傳入的applyMiddleware
函數。
//在enhancer有效的狀況下,createStore會返回enhancer(createStore)(reducer, preloadedState)。
return enhancer(createStore)(reducer, preloadedState)
複製代碼
咱們來看下剛剛的applyMiddleware
,是否是一會兒明白了呢。
return createStore => (...args) => {
// ....
}
複製代碼
到這裏應該就很容易理解redux-thunk
的實現了,他作的事情就是判斷action
類型是不是函數,若是是就執行action
,不然就繼續傳遞action
到下個 middleware
。
參考文檔: