Redux中间件原理
- Published on
- 发布于·预估阅读11分钟
- Authors
- Name
- willson-wang
redux 的 middleware 是为了增强 dispatch 而出现的
没有middleware
有middleware
那么redux中间件是怎样实现的?
我首先想到的是redux-thunk,因为这个最简单,也最好理解,它是怎么做到支持传入disptach方法的参数可以是函数,然后看了下源码实现,只有短短几行代码,如下所示
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
实现逻辑就是对传入的action做判断,如果传入的action是函数,则调用action,把dispatch、getState在传入到action函数内,方便action函数内处理完副作用之后,可以执行dispatch,也可以通过getState获取到最新的store内容
action.js
// 获取用户信息
export function getUserInfo(params) {
return async function(dispatch, getState) {
const { app } = getState()
const { userInfo } = app
if (Object.keys(userInfo).length && !params._force) {
return userInfo
}
const user = await authorize.getUserInfoPromise()
dispatch({
type: types.UPDATEUSERINFO,
payload: user
})
return {
userInfo: user
}
}
}
index.js
const mapDispathToProps = (dispath: Dispatch): Actions.AppActionsMethodTypes => {
return {
getUserInfo: (parmas) => {
// dispatch传入的action是一个函数
return dispath(Actions.getUserInfo(parmas))
}
}
}
this.props.getUserInfo({})
redux-thunk.js
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
显然redux-thunk是通过一种劫持的手段,来支持了传入的action是function的场景
那么我们在来看下为什么要 return next(action); 这个next是什么?redux中间件说的洋葱模型又是什么?
先看下redux中间件的书写及调用方式,以redux-thunk为例
redux-tunk.js
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
store.js
const middlewares = [thunk]
const middlewareEnhancer = applyMiddleware(...middlewares)
const store = createStore(createReducer(), preloadState, middlewareEnhancer)
return store
调用applyMiddleware方法传入middlewares参数
看下applyMiddleware的源码实现
export default function applyMiddleware(
...middlewares
){
return (createStore) =>
(
reducer,
preloadedState
) => {
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))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
applyMiddleware接受一个middlewares数组,然后返回一个函数,这个函数接受一个createStore参数,其实这就是一个闭包
然后我们在看下
createStore(createReducer(), preloadState, middlewareEnhancer)
redux.js
function createStore(reducer, preloadedState, enhancer) {
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(
enhancer
)}'`
)
}
return enhancer(createStore)(reducer,preloadedState)
}
}
这里判断是否传入了enhancer函数,也就是传入了applyMiddleware函数的返回值
回过头来看这里
我们在项目代码内调用
const store = createStore(createReducer(), preloadState, middlewareEnhancer)
return store
<Provider store={store}>
<Root />
</Provider>
这个store则是applyMiddleware内最终反回的
return {
...store,
dispatch
}
然后我们细看下applyMiddleware方法内的实现
const store = createStore(reducer, preloadedState)
let 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)
}
// 关键步骤一,往每个中间件函数传入getState,及dispatch这两个方法
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 关键步骤二,通过compose组合调用中间件函数,然后传入原始的store.dispatch方法
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
加入我们传入三个中间件fn1, fn2,fn3先看下chain得到的是什么
middlewares数组应该是这样的 [ // 三层函数的中间件
({getState, dispatch}) => next1 => action => next1(action),
({getState, dispatch}) => next2 => action => next2(action),
({getState, dispatch}) => next3 => action => next3(action)
]
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 传入middlewareAPI之后,得到的chain应用事这样的数组 [ // 2层函数的中间件了,此时已经可以去访问调用dispatch与getState变量了
next1 => action => next1(action),
next2 => action => next2(action),
next3 => action => next3(action)
]
我们看下compose的实现
const compose = (...fns) => {
if (!fns.length) {
return (arg) => arg
}
if (fns.length === 1) {
return fns[0]
}
return fns.reduce((a, b) => {
return (...args) => {
return a(b(...args))
}
})
}
compose(fn1, fn2, fn3) =>
最终返回的事一个函数 (...args) => {return a(b(...args))}
我们用个例子来试下
function fn1(...args) {
console.log('fn1', ...args)
return 1
}
function fn2(...args) {
console.log('fn2', ...args)
return 2
}
function fn3(...args) {
console.log('fn3', ...args)
return 3
}
我们传入一个函数,
const result1 = compose(fn1)
result1(999)
打印结果:fn1 999
传入两个函数,会遍历一次
const result2 = compose(fn1, fn2)
result2(999)
打印结果:fn2 999; fn1 2; 1
我们传入三个函数,会遍历两次
const result3 = compose(fn1, fn2, fn3)
result3(999)
打印结果:fn3 999; fn2 3; fn1 2; 1
所以compose(fn1, fn2, fn3) === (...args) => fn1(fn2(fn3(...args)))
compose的作用:从右至左依次执行函数,将上一个函数的执行结果传入到下一个函数
// 在来看这行代码
dispatch = compose(...chain)(store.dispatch)
拆开来看
第一步
const composeMiddleware = compose(...chain)
composeMiddleware的值事这样的 (...args) => fn1(fn2(fn3(...args)))
第二步
dispatch = composeMiddleware(store.dispatch)
dispatch的值如下所示fn1(fn2(fn3(store.dispatch))),也就是第一个中间件函数最终返回的值,通过前面的步骤我们可以知道chain数组如下所示
[
next1 => action => next1(action),
next2 => action => next2(action),
next3 => action => next3(action)
]
那么fn1(fn2(fn3(store.dispatch)))调用过程如下所示
fn3(store.dispatch)调用 传入参数 next3(store.dispatch) 返回值为action => store.dispatch(action)
fn2(fn3调用返回参数)调用 传入参数 next2(action => store.dispatch(action)) 返回值为action => (action => store.dispatch(action))(action)
fn1(fn2调用返回参数)调用 传入参数 next1(action => (action => store.dispatch(action))(action)) 返回值为 (action => (action => store.dispatch(action))(action))(action)
调用完之后的返回值是fn1 next => action => next(action) 调用的返回值 action => next(action), 这个next就是fn2调用返回的值
dispatch = action => next1(action) (也就是fn1的返回值)
实际上将每个fn函数都执行一边,第一个执行的函数,也就是最右边的函数,会接受到一个store.dispatch函数,后面的函数接受到的都是上一个函数执行的返回结果
我们看下洋葱模型,定义三个中间件logger1、logger2、logger3
function logger1(...args) { // 这一层函数的目的,可以帮助中间件定义的时候传入一些自定义参数
return function({dispatch, getState}) { // 这一层函数的目的是,接受getState,dispatch函数,可以拿到store内容,并做一些操作
return function (next) { // 接受store.dispatch参数或者下一个中间件的返回函数
return function (action) {
console.log('logger1 enter', action)
const result = next(action)
console.log('logger1 outer', result)
return result
}
}
}
}
const log1 = logger1()
function logger2(...args) {
return function({dispatch, getState}) {
return function (next) {
return function (action) {
console.log('logger2 enter', action)
const result = next(action)
console.log('logger2 outer', result)
return result
}
}
}
}
const log2 = logger2()
function logger3(...args) {
return function({dispatch, getState}) {
return function (next) {
return function (action) {
console.log('logger3 enter', action)
const result = next(action)
console.log('logger3 outer', result)
return result
}
}
}
}
const log3 = logger3()
const obj = {
getState: () => {},
dispatch: () => {}
}
const chain = [log1(obj), log2(obj), log3(obj)]
var dispatch = (...args) => {
console.log('原始 dispatch')
return args
}
const newDispatch = compose1(...chain)(dispatch)
console.log('last', newDispatch)
newDispatch({
type: 'INIT',
payload: {
a: 123
}
})
// 执行结果为
// logger1 enter {type: "INIT", payload: {…}}
// logger2 enter {type: "INIT", payload: {…}}
// logger3 enter {type: "INIT", payload: {…}}
// 原始 dispatch
// logger3 outer {type: "INIT", payload: {…}}
// logger2 outer {type: "INIT", payload: {…}}
// logger1 outer {type: "INIT", payload: {…}}
这个就是洋葱模型,如下图所示
到这里我们已经知道,redux的中间件是怎么去设计的了,同时也知道中间件为什么需要定义4层函数,同时我们在看之前的一个细节
// 这里定义了一次dispatch函数
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) // 这里传入的dispatch方法,内部调用的就是上面定义的dispatch方法,这里只所以没有直接传入store.dispatch,就是为了避免中间件在初始化的时候,或者执行的时候去修改了原始的dispatch方法
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
总体来说,中间件的实现的关键思路是使用compose组合的方式,将一系列函数组合成如下函数(...args) => fn1(fn2(fn3(...args))),即将最后一个函数调用的返回值当成参数传入到前一个函数,当第一个函数被调用的时候,依次调用传入的参数,直到把传入的参数传入最后一个函数;