多數(shù)
redux
初學(xué)者都會使用redux-thunk
這個中間件來處理異步請求(比如我)
本來寫這篇文章只是想寫寫redux-thunk
间狂,然后發(fā)現(xiàn)還不夠瑟幕,就順便把middleware
給過了一遍学辱。
為什么叫thunk
?
thunk
是一種包裹一些稍后執(zhí)行的表達(dá)式的函數(shù)姑裂。
redux-thunk
源碼
所有的代碼就只有15行钮惠,我說的是真的僻他。。 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;
代碼很精簡,但是功能強(qiáng)大坤候,所以非常有必要去了解一下。
redux-middleware是個啥
上圖描述了一個redux
中簡單的同步數(shù)據(jù)流動的場景址貌,點(diǎn)擊button
后铐拐,dispatch
一個action
,reducer 收到 action 后练对,更新state
后告訴UI
遍蟋,幫我重新渲染一下。
redux-middleware
就是讓我們在dispatch
action
之后螟凭,在action
到達(dá)reducer
之前虚青,再做一點(diǎn)微小的工作,比如打印一下日志什么的螺男。試想一下棒厘,如果不用middleware
要怎么做,最navie
的方法就是每次在調(diào)用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());
}
- 現(xiàn)在問題來了,每次
dispatch
的時候都要import
這個函數(shù)進(jìn)來土辩,有點(diǎn)麻煩是不是支救,那怎么辦呢?
既然dispatch
是逃不走的拷淘,那就在這里動下手腳各墨,redux
的store
就是一個有幾種方法的對象,那我們就簡單修改一下dispatch
方法启涯。
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
贬堵,都能實(shí)現(xiàn)想要的功能了恃轩,這就是中間件的雛形。
- 現(xiàn)在問題又來了扁瓢,大佬要讓你加一個功能咋辦详恼?比如要異常處理一下
接下來就是怎么加入多個中間件了。
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
進(jìn)行了重寫引几,依次調(diào)用這個兩個函數(shù)之后昧互,就能實(shí)現(xiàn)打印日志和異常處理的功能。
patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)
- 之前我們寫了一個函數(shù)來代替了
store.dispatch
伟桅。如果直接返回一個新的dispatch
函數(shù)呢敞掘?
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
等于這個新返回的函數(shù),再另外寫一個函數(shù)楣铁,把上面兩個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)
會返回一個新的函數(shù),賦值給store.dispatch
盖腕,下一個middleware
就能拿到一個的結(jié)果赫冬。
接下來就可以這樣使用了,是不是優(yōu)雅了一些溃列。
applyMiddlewareByMonkeypatching(store, [logger, crashReporter])
我們?yōu)槭裁催€要重寫dispatch
呢劲厌?當(dāng)然啦,因為這樣每個中間件都可以訪問或者調(diào)用之前封裝過的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
的參數(shù)傳入,而不是直接從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
}
}
}
柯里化就是把接受多個參數(shù)的函數(shù)編程接受一個單一參數(shù)(注意是單一參數(shù))的函數(shù)硼婿,并返回接受余下的參數(shù)且返回一個新的函數(shù)。
舉個例子:
const sum = (a, b, c) => a + b + c;
// Curring
const sum = a => b => c => a + b + c;
用ES6
的箭頭函數(shù)禽车,看起來更加舒服加酵。
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
了。
// 注意:這是簡單的實(shí)現(xiàn)
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)
來連接钦勘。
現(xiàn)在來看下redux
中applyMiddleware
是怎么實(shí)現(xiàn)的?
/**
* 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))).
*/
// 就是把上一個函數(shù)的返回結(jié)果作為下一個函數(shù)的參數(shù)傳入亚亲, 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
最后返回的也是一個函數(shù)彻采,接收一個參數(shù)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
中實(shí)現(xiàn)的。
export default function createStore(reducer, preloadedState, enhancer){
...
}
createStore
接受三個參數(shù):reducer
, initialState
, enhancer
肛响。enhancer
就是傳入的applyMiddleware
函數(shù)岭粤。
//在enhancer有效的情況下,createStore會返回enhancer(createStore)(reducer, preloadedState)特笋。
return enhancer(createStore)(reducer, preloadedState)
我們來看下剛剛的applyMiddleware
剃浇,是不是一下子明白了呢。
return createStore => (...args) => {
// ....
}
到這里應(yīng)該就很容易理解redux-thunk
的實(shí)現(xiàn)了猎物,他做的事情就是判斷action
類型是否是函數(shù)虎囚,如果是就執(zhí)行action
,否則就繼續(xù)傳遞action
到下個 middleware
蔫磨。
參考文檔: