本文已参与「新人创作礼」活动,一起开启掘金创作之路。
首先我们来了解一下redux的几个基本概念:
Action是把数据从应用传到store的有效载荷。它是store数据的唯一来源。一般来说你会通过store.dispatch()将action传到storeReducers指定了应用状态的变化如何响应actions并发送到store的Store维护了想要存储的state以及用来满足Action和Reducers使用的API;
redux的一个粗略的是处理过程就是:
什么是redux中间件?
redux中间件其实是提供了位于 action 被发起之后,到达 reducer 之前的扩展点,暂时简单理解为这样(其实不然,实际详细结构往后看oVo!)
中间件格式
箭头函数风格:
({getState, dispatch}) => next => action => { ... }
相当于
({getState, dispatch}) => {
return next => {
return action => { ... }
}
}
至于为什么要这种格式,可以看下文。
applyMiddleware
中间件的格式是固定的,那么它是怎么发挥作用的呢?先来看一下applyMiddleware的源码实现:
export default function applyMiddleware(
...middlewares: Middleware[] // 中间件队列
): StoreEnhancer<any> {
return (
createStore: StoreEnhancerStoreCreator
) => <S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => {
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 每一个中间件传入MiddlewareAPI
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
由applyMiddleware的源码可以看出,该函数的主要作用就是将middleware队列依次处理,以对象形式传入getState和dispatch;然后中间件队列中每个中间件变成了以下格式:
next => action => { ... }
然后再作为参数由compose函数处理。
compose
compose的实现源码如下:
export default function compose(...funcs: Function[]) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
// 主要看这里
return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}
从return的处理方式可以看出,中间件队列经过reduce处理以后会是这个样子:
举个栗子:
[m1, m2, m3]
compose处理后:
(...args) => m1(m2(m3(...args)))
可以看出m3的返回结果作为参数传给了m2执行,m2的返回结果作为参数传给了m1执行。由此可以看出,中间件队列中中间件的执行顺序是从右到左倒序执行的m3 -> m2 -> m1且前一个函数返回结果作为参数给下一个函数。在applyMiddleware中store.dispatch将作为compose返回函数的参数传入,而store.dispatch的返回值就是要 dispatch 的 action。由此可以做一些定制化的操作了,比如简单介绍一下以下栗子:
m1
({dispatch, getState}) => (next) => (action) => {
console.log('m1 pre next')
let result = next(action);
console.log('m1 after next')
return result;
}
m2
({dispatch, getState}) => (next) => (action) => {
// action经过此中间件提交之前,do something。。。
console.log('m2 pre next')
let result = next(otherAction);
console.log('m2 after next')
// action经过此中间件提交之后,do something。。。
return result;
}
compose处理以后伪代码
(...args) => m1(m2(...args))
然后store.dispatch作为参数传入,执行结果变为:
m1(m2(store.dispatch)
然后m2执行以后:
m1((action) => {
console.log('m2 pre next')
let result = next(otherAction);
console.log('m2 after next')
return result;
})
类似等价于:
(() => {
console.log('m1 pre next')
let result = (() => {
console.log('m2 pre next')
let result = store.dispatch(action);
console.log('m2 after next')
return result
})()
console.log('m1 after next')
return result;
})()
其结构有一个较为通俗的叫法:洋葱模型。
洋葱模型
结构就是这样
{'m1 pre next'{'m2 pre next'{store.dispatch}'m2 after next'}'m1 after next'}
用图表示就是
这才是中间件的真实结构~