# 深入了解redux的中间件机制

# redux中间件基本原理初认识

我们先来体会一下redux中间件的基本原理,假设现在有一个需求,我们希望每次dispatch的时候打印出上一个状态的值,当前的action以及下一个状态的值,通常我们会这样做,重写dispatch方法




 











// store/index.js

import {createStore} from 'redux'
import reducer from './reducers'
let store =  createStore(reducer)
let oldDispatch = store.dispatch // 缓存原生的siapatch
store.dispatch = function(action) { // 重写dispatch方法
    console.log('prev state', store.getState())
    console.log('action', action)
    oldDispatch(action)
    console.log('next state', store.getState())
}

export default store

其实这就是我们redux中间件最简单的原理了,先把原生的dispatch方法缓存起来,然后重写dispatch方法,最后调用缓存起来的dispatch方法,学过vue的同学可能知道,这个思路是不是和vue实现数组方法触发响应式一样呀

# 实现 redux-logger

既然我们已经知道了redux中间件的大致思路了,然后我们来看下redux中怎么使用中间件,然后实现和使用我们的第一个中间件




 














import {createStore, applyMiddleware} from 'redux'
import reducer from './reducers'

let store = applyMiddleware(logger)(createStore)(reducer)

function logger({ getState, dispatch }) {
    return function(next) { // next 代表下一个中间件
        return function(action) { // action动作
            console.log('prev state', getState())
            console.log('action', action)
            next(action)
            console.log('next state', getState())
        }
    }
}

export default store

我们可以发现我们引入了一个新的函数applyMiddleware, 它接收的第一个参数是中间件, 第二个参数是 createStore方法, 第三个参数是reducer

然后值得一提的是, redux的中间件格式都是固定的,如上述logger中间件一致,至于为什么是这样的格式,这一点我们暂时不用理会,到下文中实现联级(将多个中间件连成一个类似于洋葱模型的结构)的时候你才会体验到这种结构的妙处.

我们在上文中之后中间件的基本原理无非是重写dispatch方法,那么我们大致可以猜到applyMiddleware方法的作用就是在内部重写dispatch方法,然后返回store。我们试下来实现它的基本结构吧




 





function applyMiddleware(middleware) {
    return function(createStore) {
        return function(reducer) {
            let store = createStore(reducer)
            return store
        }
    }
}

现在大致知道为什么需要将createStorereducer 作为参数传入了吧,那现在我们试下改动他,让他支持我们的logger中间件




 














function applyMiddleware(middleware) {
    return function(createStore) {
        return function(reducer) {
            let store = createStore(reducer)
            let middlewareApi = {
                getState: store.getState,
                dispatch: store.dispatch,
            }
            middleware = middleware(middlewareApi)
            let dispatch = middleware(store.dispatch) // 下一个中间件是原生的dispatch
            return {
                ...store,
                dispatch,
            }
        }
    }
}

其实是不是就是我们的中间件基本原理,重写dispatch方法,那我们接下来在实现几个常用的中间件吧

# 实现 redux-thunk

先来了解一下redux-thunk的作用,我们知道原生redux中的dispatch只可以派发action,换句话来说就是只可以派发对象,那redux-thunk的作用就是让我们的dispatch可以派发一个函数,例如我们先写一个点击延迟一毫秒加一的actionCreator




 







function asyncincrement(payload) {
    return function(dispatch) {
        setTimeout(() => {
            dispatch({
                type: types.INCREMENT,
                payload,
            })
        }, 1000)
    }
}

我们可以发现这个actionCreator返回的是一个函数,不是一个对象,我们原生的dispatch方法肯定不支持这种写法,所以redux-thunk中间件就出来了,我们来实现一下




 







function thunk({ getState, dispatch }) {
    return function(next) {
        return function(action) {
            if (typeof action === 'function') { //如果是一个函数 函数执行 将 dispatch, getState作为参数传入
                return action(dispatch, getState)
            }
            next(action)
        }
    }
}

# 实现 redux-promise

同样道理,我们也想要dispatch可以派发promise, 所以redux-promise来了,我们先来看下actionCreator, 功能也是延迟一秒+1




 







function promiseincrement(payload) {
    return new Promise((resolve) =>{
        setTimeout(() => {
            resolve({
                type: types.INCREMENT,
                payload,
            })
        }, 1000)
    })
}

那我们来实现一下redux-promise





 






function promise({ getState, dispatch }) {
    return function(next) {
        return function(action) {
            if (typeof action.then === 'function' && action.then) { //判断是不是一个promise
                return action.then(dispatch, getState)
            }
            next(action)
        }
    }
}

# applyMiddleware优化

TIP

我们先不要急,一步一步来,很快一切谜底就揭晓了

在实现中间件联级之前,我们需要优化一下applyMiddleware 方法




 














function applyMiddleware(middleware) {
    return function(createStore) {
        return function(reducer) {
            let store = createStore(reducer)
            let middlewareApi = {
                getState: store.getState,
                dispatch: store.dispatch,
            }
            middleware = middleware(middlewareApi)
            let dispatch = middleware(store.dispatch) // 下一个中间件是原生的dispatch
            return {
                ...store,
                dispatch,
            }
        }
    }
}

我们可以发现, middlewareApi 里面的 dispatch 是不是永远取得是原生store下面的dispatch啊,这个很明显不符合我们中间件洋葱模型。我们希望的是dispatch是包装后的dispatch,我们画个图来理解一下,假设我们现在中间件包了2层

An image

按照之前的写法,我们执行完第一个中间件thunk之后,是不是直接走的原生的store.dispatch

我们看这行代码



let dispatch = middleware(store.dispatch)

这样肯定不行吧,因为我们绕过了logger中间件,我们的预期是如果thunk中间件匹配上了,然后改造完dispatch之后,从头再来,然后执行logger中间件吧。我们来改造下代码




 















function applyMiddleware(middleware) {
    return function(createStore) {
        return function(reducer) {
            let store = createStore(reducer)
            let dispatch
            let middlewareApi = {
                getState: store.getState,
                dispatch: (...args) => dispatch(...args),
            }
            middleware = middleware(middlewareApi)
            dispatch = middleware(store.dispatch) 
            return {
                ...store,
                dispatch,
            }
        }
    }
}

我们先声明一个dispatch,第一次执行的时候是undefined,所以无需理会,当代码走到



 dispatch = middleware(store.dispatch)

的时候,我们可以知道dispatch已经给赋值于thunk中间件(也就是第一个中间件改造后的dispatch或者原生store.dispatch),这样是不是就可以达到我们的目的了

# compose方法

redux中间件的设计很绕,可能看到这还是很迷糊的,这个是很正常的,一切待解释到如何实现联级中间件的时候,一切都懂了,但是在那之前,我们先来说一个 compose 方法




 










function add1(str) {
    return str + 1
}

function add2(str) {
    return str + 2
}

function add3(str) {
    return str + 3
}

let result = add3(add2(add1(1))) // 7

这是很常见的一种函数内嵌调用吧,compose 方法的作用就是解决这种比较难看的内嵌调用的




 














function add1(str) {
    return str + 1
}

function add2(str) {
    return str + 2
}

function add3(str) {
    return str + 3
}

let result = compose(add3, add2, add1)(1)

function compose(...fns) {
    return fns.reduce((a, b) => (...args) => a(b(...args)))
}

这里就不仔细说他的内部原理了,有兴趣的同学可以自己打输出断点研究下

# 实现联级

我们先来看使用方法



let store = applyMiddleware(promise, logger, thunk)(createStore)(reducer)

我们的applyMiddleware的第一个参数传入的是全部的中间件了,我们先来实现它,然后再来体会逻辑




 















function applyMiddleware(...middlewares) {
    return function(createStore) {
        return function(reducer) {
            let store = createStore(reducer)
            let dispatch
            let middlewareApi = {
                getState: store.getState,
                dispatch: (...args) => dispatch(...args),
            }
            middlewares = middlewares.map(middleware => middleware(middlewareApi))
            dispatch = compose(...middlewares)(store.dispatch)
            return {
                ...store,
                dispatch,
            }
        }
    }
}

我们来一步一步分析,首先我们看下经过处理之后的middlewares是啥

An image

我们可以发现, middlewares 是全部中间件第一次执行的返回值,也就是

An image

的这个部分,然后根据我们对compose方法的理解,会将上述框起来的三个函数转化成类似于add3(add2(add1(1)))的写法,也就是类似于

An image

当走到第一个中间件redux-promise的时候,如果匹配上了,就进行中间件重写dispatch,返回新的dispatch。如果匹配不上就走next(action), 但是我们经过compose方法处理之后,这个next的值明显是第二个中间件函数执行的返回值,然后第二个中间件开始走相同的逻辑,直到最后走到了原生的store.dispatch方法

# 总结

一开始接触redux的中间件的时候也很闷,不知道为什么中间件一定要遵守这种结构的写法,也不知道内部是如何处理中间件的,但是慢慢深入理解和多打几次代码和断点之后发现到了这个结构设计的巧妙之处,继续加油