middleware 的由来
在业务中需要打印每一个 action 信息来调试,又或者希望 dispatch 或 reducer 拥有异步请求的功能。面对这些场景时,一个个修改 dispatch 或 reducer 代码有些乏力,我们需要一个可组合的、自由增减的插件机制,Redux 借鉴了 Koa 中 middleware 的思想,利用它我们可以在前端应用中便捷地实现如日志打印、异步请求等功能。
比如在项目中,进行了如下调用后,redux 就集成了 thunk 函数调用以及打印日志的功能。
1 | import thunk from 'redux-thunk' |
下面追本溯源,来分析下源码。
applyMiddleware 调用入口
1 | export default function createStore(reducer, preloadedState, enhancer) { |
由上述 createStore 源码发现,applyMiddleware 会进行 applyMiddleware(thunk, logger)(createStore)(reducer, preloadedState)
的调用。
1 | export default function applyMiddleware(...middlewares) { |
可以发现 applyMiddleware 的作用其实就是返回加工过的 dispatch,下面会着重分析 middlewares 是如何串行起来的以及 dispatch 是如何被加工的。
串行 middleware
1 | const middlewareAPI = { |
观察上述代码后发现每个 middleware 都会传入参数 middlewareAPI,来看下中间件 logger 的源码 以及 redux-thunk 的源码, 发现中间件接受的第一个参数正是 ({ dispatch, getState })
1 | // logger 源码 |
1 | // redux-thunk 源码 |
dispatch 是如何被加工的
接着上个小节,在 dispatch = compose(...chain)(store.dispatch)
中发现了 compose 函数,来看下 compose 的源码
1 | export default function compose(...funcs) { |
compose 源码中的 funcs.reduce((a, b) => (...args) => a(b(...args)))
算是比较重要的一句,它的作用是返回组合参数后的函数,比如 compose(f, g, h) 等价于 (…args) => f(g(h(…args))),效果图如下所示,调用 this.props.dispatch() 后,会调用相应的中间件,最终会调用 redux 原生的 store.dispatch(),并且可以看到中间件调用的形式类似数据结构中的栈(先进后出)。
拿上个小节提到的 logger、redux-thunk 中间件为例,其 middleware 的内部串行调用方式如下,从而完成了 dispatch 功能的增强(支持如 this.props.dispatch(func)
的调用以及日志功能)。具体可以看 项目中的运用
1 | action => { |
参考文献
深入React技术栈