共计 3225 个字符,预计需要花费 9 分钟才能阅读完成。
由于一直用业界封装好的如 redux-logger、redux-thunk 此类的中间件,并没有深入去了解过 redux 中间件的实现方式。正好前些时间有个需求需要对 action 执行时做一些封装,于是借此了解了下 Redux Middleware 的原理。
* 中间件概念
首先简单提下什么是中间件,该部分与下文关系不大,可以跳过。来看眼这个经典的图。
不难发现:
不使用 middleware 时,在 dispatch(action) 时会执行 rootReducer,并根据 action 的 type 更新返回相应的 state。
而在使用 middleware 时,简言之,middleware 会将我们当前的 action 做相应的处理,随后再交付 rootReducer 执行。
简单实现原理
比如现有一个 action 如下:
function getData() {
return {
api: ‘/cgi/getData’,
type: [GET_DATA, GET_DATA_SUCCESS, GET_DATA_FAIL]
}
}
我们希望执行该 action 时可以发起相应请求,并且根据请求结果由定义的 type 匹配到相应的 reducer,那么可以自定义中间件处理该 action,因此该方法封装成中间件之前可能是这样的:
function dispatchPre(action, dispatch) {
const api = action.api;
const [fetching_type, success_type, fail_type] = action.type;
// 拉取数据
const res = await request(api);
// 拉取时状态
dispatch({type: fetching_type});
// 成功时状态
if (res.success) {
dispatch({type: success_type, data: res.data});
console.log(‘GET_SUCCESS’);
}
// 失败时状态
if (res.fail) {
dispatch({type: fail_type});
console.log(‘GET_FAIL’);
};
}
// 调用: dispatchPre(action())
那如何封装成中间件,让我们在可以直接在 dispatch(action) 时就做到这样呢?可能会首先想到改变 dispatch 指向
// 储存原来的 dispatch
const dispatch = store.dispatch;
// 改变 dispatch 指向
store.dispatch = dispatchPre;
// 重命名
const next = dispatch;
截止到这我们已经了解了中间件的基本原理了~
源码分析
了解了基本原理能有助于我们更快地读懂 middleware 的源码。一般我们会这样添加中间件并使用。
createStore(rootReducer, applyMiddleware.apply(null, […middlewares]))
接下来我们可以重点关注这两个函数 createStore、applyMiddleware
CreateStore
// 摘至 createStore
export function createStore(reducer, rootState, enhance) {
…
if (typeof enhancer !== ‘undefined’) {
if (typeof enhancer !== ‘function’) {
throw new Error(‘Expected the enhancer to be a function.’)
}
/*
若使用中间件,这里 enhancer 即为 applyMiddleware()
若有 enhance,直接返回一个增强的 store 对象
*/
return enhancer(createStore)(reducer, preloadedState)
}
…
}
ApplyMiddleware
再看看 applyMiddleware 做了什么,applyMiddleware 函数非常简单,就十来行代码,这里将其完整复制出来。
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)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(…chain)(store.dispatch)
return {
…store,
dispatch
}
}
}
执行步骤
可以将其主要功能按步骤划分如下:1、依次执行 middleware。将 middleware 执行后返回的函数合并到一个 chain 数组,这里我们有必要看看标准 middleware 的定义格式,如下
export default store => next => action => {}
// 即
function (store) {
return function(next) {
return function (action) {
return {}
}
}
}
那么此时合并的 chain 结构如下
[…,
function(next) {
return function (action) {
return {}
}
}
]
2、改变 dispatch 指向。想必你也注意到了 compose 函数,compose 函数如下:[…chain].reduce((a, b) => (…args) => a(b(…args))) 实际就是一个柯里化函数,即将所有的 middleware 合并成一个 middleware,并在最后一个 middleware 中传入当前的 dispatch。这里再使用一个简单的例子方便大家理解。
// 假设 chain 如下:
chain = [
a: next => action => {console.log(‘ 第 1 层中间件 ’) return next(action) }
b: next => action => {console.log(‘ 第 2 层中间件 ’) return next(action) }
c: next => action => {console.log(‘ 根 dispatch’) return next(action) }
]
调用 compose(…chain)(store.dispatch) 后返回 a(b(c(dispatch)))。可以发现已经将所有 middleware 串联起来了,并同时修改了 dispatch 的指向。最后看一下这时候 compose 执行返回,如下
dispatch = a(b(c(dispatch)))
// 调用 dispatch(action)
// 执行循序
/*
1. 调用 a(b(c(dispatch)))(action) __print__: 第 1 层中间件
2. 返回 a: next(action) 即 b(c(dispatch))(action)
3. 调用 b(c(dispatch))(action) __print__: 第 2 层中间件
4. 返回 b: next(action) 即 c(dispatch)(action)
5. 调用 c(dispatch)(action) __print__: 根 dispatch
6. 返回 c: next(action) 即 dispatch(action)
7. 调用 dispatch(action)
*/