blog
blog copied to clipboard
理解 Middleware
如果你使用过 Express 或者 Koa 等服务端框架, 那么应该对 middleware 的概念不会陌生。 在这类框架中,middleware 是指可以被嵌入在框架接收请求到产生响应过程之中的代码。例如,Express 或者 Koa 的 middleware 可以完成添加 CORS headers、记录日志、内容压缩等工作。middleware 最优秀的特性就是可以被链式组合。你可以在一个项目中使用多个独立的第三方 middleware。
相对于 Express 或者 Koa 的 middleware,Redux middleware 被用于解决不同的问题,但其中的概念是类似的。它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
了解它的演化过程是一件相当重要的事。我们将以记录日志为例,引导你体会从分析问题到通过构建 middleware 解决问题的思维过程。
如果我们直接替换 store 实例中的 dispatch 函数会怎么样呢?
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
}
无论我们在哪里发起 action,保证都会被记录。
在之前,我们用自己的函数替换掉了 store.dispatch
。如果我们不这样做,而是在函数中返回新的 dispatch
呢?
function logger(store) {
let next = store.dispatch
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
我们可以在 Redux 内部提供一个可以应用到 store.dispatch
中的辅助方法:
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
// 在每一个 middleware 中变换 dispatch 方法。
middlewares.forEach(middleware =>
store.dispatch = middleware(store)
)
}
然后像这样应用多个 middleware:
applyMiddleware(store, [logger])
为什么我们要替换原来的 dispatch
呢?当然,这样我们就可以在后面直接调用它,但是还有另一个原因:就是每一个 middleware 都可以操作前一个 middleware 包装过的 store.dispatch
,现在已经将 middleware 串连起来了。
但是,还有另一种方式来实现这种链式调用的效果。可以让 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
}
}
}
ES6 的箭头函数从而看起来更舒服一些:
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
这正是 Redux middleware 的样子。
Middleware 接收了一个 next()
的 dispatch 函数,并返回一个 dispatch 函数,返回的函数会被作为下一个 middleware 的 next()
,以此类推。由于 store 中类似 getState()
的方法依旧非常有用,我们将 store
作为顶层的参数,使得它可以在所有 middleware 中被使用。
我们可以写一个 applyMiddleware()
方法替换掉原来的 applyMiddleware()
。在新的 applyMiddleware()
中,我们取得最终完整的被包装过的 dispatch()
函数,并返回一个 store 的副本:
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 })
}
这与 Redux 中 applyMiddleware() 的实现已经很接近了,现在开始源码。
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)))
}
function applyMiddleware(...middlewares) {
return (createStore) => (reducer, ...args) => {
const 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
}
}
}
redux 中 applyMiddleware()
的用法:
applyMiddleware(logger)(createStore)(reducer)