多数
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
}
}
}
复制代码
到这里咱们来看一下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
。
参考文档: