中间件,它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点,看到图片中middleware 所处的位置没,就是那个地方。
先回顾一下 dispatch 函数的用法
dispatch({type: 'INCREMENT'})
{type: 'INCREMENT'}
这个参数我们称为action,Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
我们需要做一个记录日志的功能,我们直接替换 store 实例中的 dispatch 函数。
function logger(store) {
let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
感觉还是不太舒服,不过利用它我们做到了我们想要的。
在之前,我们用自己的函数替换掉了 store.dispatch
。如果我们不这样做,而是在函数中返回新的 dispatch 呢?
function logger(store) {
let 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
}
}
为了能够在内部使用,我们需要通过一个辅助方法处理一下,middleware 包装是从左往右包装,执行是从右往左执行。
function applyMiddlewareByMonkeypatching(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
// 在每一个 middleware 中变换 dispatch 方法。
middlewares.forEach(middleware =>
store.dispatch = middleware(store)
)
}
然后像这样应用多个 middleware:
applyMiddlewareByMonkeypatching(store, [ logger, crashReporter ])
为什么我们要替换原来的 dispatch 呢?当然,这样我们就可以在后面直接调用它,但是还有另一个原因:就是每一个 middleware 都可以操作(或者直接调用)前一个 middleware 包装过的 store.dispatch
。但是,还有另一种方式来实现这种链式调用的效果。可以让 middleware 以方法参数的形式接收一个 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 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()
方法替换掉原来的 applyMiddlewareByMonkeypatching()
。在新的 applyMiddleware()
中,我们取得最终完整的被包装过的 dispatch()
函数,并返回一个 store 的副本:
// 警告:这只是一种“单纯”的实现方式!
// 这 *并不是* Redux 的 API.
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 })
}
源码是怎样实现的applyMiddleware函数
function compose(...funcs) {
if (funcs.length === 0) {
return
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
function applyMiddleware(...middleware) {
return (createStore) => (reducer, ...args) => {
let store = createStore(reducer, ...args)
let dispatch = () => {
throw new Error('现在还不能用')
}
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
applyMiddleware(logger)(createStore)(reducer);