Redux中的中间件思想
要想理解redux 的中间件思想, 我们先 看下compose这个文件做了什么, 这个文件其实做的事情十分简单。主要是用到函数式编程中的组合概念, 将多个函数调用组合成一个函数调用。
其实主要是reduce 的这个api,为了方面大家理解, 我就简单手写下数组reduce 的实现:
ok 这东西其实就是个累加器按照某种方式呗。我们接下来就直接进入compose函数话不多说直接看代码:
// 参数是函数数组 export default function compose(...funcs: Function[]) { // 处理边界情况 if (funcs.length === 0) { return <T>(arg: T) => arg } // 数量为1 就没有组合的必要了 if (funcs.length === 1) { return funcs[0] } // 主要是下面这一行代码, 你细品👌 return funcs.reduce((a, b) => (...args: any) => a(b(...args)))} 复制代码
OK, 我现在还是带大家分析下这行代码,同样是我举例子说明: 假设我们有这个3个函数:
funcs = [fa, fb, fc]
由于没有出初始值累加器的就是 fa 经过一次遍历后, accumulateur 变为下面的样子:
let m= (...args) => fa(fb(...args))
在经过一次遍历后, 此时b = fc 此时accumulateur 变为下面的样子:
**(...args) => m(fc(...args))**
我们将fc(...args) 看做一个整体带入上面m的函数 所以就变成了下面的样子
(...args)=> fa(fb(fc(...args)))
OK到这里 就大工告成了,fa, fb, fc 我们可以想象成 redux 的3个中间件, 按照顺序传进去,
当这个函数被调用的时候也会按照 fa, fb, fc 顺序调用。前面铺垫结束就直接开始分析? compose 是如何 和Redux 做结合的。
applyMiddleWare
我先把整体结构分析下, 和函数参数分下:
// applyMiddlerware 会使用“...”运算符将入参收敛为一个数组 export default function applyMiddleware(...middlewares) { // 它返回的是一个接收 createStore 为入参的函数 return createStore => (...args) => { ...... } } 复制代码
createStore 就是 上面我们分析过的 创建数据中心Store ,而 args 主要是有两个, 还是createStore 两个约定入参 一个是reducer, 一个是 initState。
enhance-dispatch
接下来就是比较核心的, 改写dispacth, 为什么要改写dispatch, 还是举个例子说明。 不知道大家还记不记得 dispacth 接受的action 只能是对象, 如果不是对象的话会直接报类型错误。如图:
OK社区比较有名的redux-thunk 中间件, dispatch 可以接受一个函数, 难道是她绕过了我们的检查,那肯定不是,计算机肯定不会骗人的。 那只有一个原因。
应用了中间件的dispatch 和 没有用中间的dispatch 肯定是不等的,
dispatch = enhancer(dispacth) 肯定是增强的至于怎么个增强法 继续往下看。
const store = createStore(...args) let chain = [] const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } chain = middlewares.map(middleware => middleware(middlewareAPI)) // 绑定 {dispatch和getState} dispatch = compose(...chain)(store.dispatch) 复制代码
从代码中可以看到的第一步 其实就是将 getState 和 dispatch 作为中间件中的闭包使用, 有人会这里提问,这里为什么是匿名函数 ? 而不是store.dispatch ? 这里你可以自行先🤔下, 后面解答。
可能很多人看到这里还是很懵, 还是举个例子说明, 还记得上面的 fa , fb, fc 接下来 我就把他们展开。
function fa(store) { return function(next) { return function(action) { console.log('A middleware1 开始'); next(action) console.log('B middleware1 结束'); }; }; } function fb(store) { return function(next) { return function(action) { console.log('C middleware2 开始'); next(action) console.log('D middleware2 结束'); }; }; } function fc(store) { return function(next) { return function(action) { console.log('E middleware3 开始'); next(action) console.log('F middleware3 结束'); }; }; } 复制代码
ok 我们一步一步分析, 看到底做了什么。
chain = middlewares.map(middleware => middleware(middlewareAPI)) 复制代码
很显然 我们 的 middlewares = [fa, fb, fc] map 之后返回一个新的 chain 这时候的chain 应该是下面 这样子的:
chain = [ (next)=>(action)=>{...}, (next) => (action) => {...}, (next) => (action) => {...} ]
只不过chain中的每一个元素 都有getSate,dispatch 的闭包而已。
ok 继续往下走就到了compose 函数 还记得上面我们compose(fa, fb, fc ) 返回值是什么?
(...args)=> fa(fb(fc(...args)))
对的就是这个东西, 我们这里的chain 也是一样的, 所以这里的 dispatch 就变成了增强的dispatch 我们一起看下
dispatch = fa(fb(fc(store.dispacth)))
看到这里有人就问? 每一个中间件的next 和 原先的store.dispacth 有什么不同 ? 这和洋葱模型有什么关系?
那我就带你一步一步分析, 这里的dispatch 就是指的是当前的 fa(fb(fc(store.dispatch))) , 我们可以直接函数调用来分析,
fa 的参数 是 fb(fc(store.dispatch)) , 由于依赖fb, 所以调用fb, 然后发现fb 依赖的参数是 fc(store.dispacth), 紧接着又开始调用fc, ok 到这里终于结束了, 终于没有依赖了。 所以从上面的过程我们可以得到 next 其实是他上个中间件的副作用, 最后一个中间件的next 就是****store.dispatch。
副作用: 每个中间件的中的 (action )=> {...}
我用流程图表示整个洋葱模型流程:
当我调用dispatch 的时候, 先打印 E, 然后发现 next 是副作用fb, 然后调用副作用fb, 打印c,发现 next 竟然是 副作用fc ,再去调用fc, 打印A, next 这时候就是 store.dispacth, 调用结束, 打印 b, 然后打印D, 最后打印 F 。 这样的一系列操作是不是有点像洋葱模型, 如果我可以一层一层拨开你的心, 哈哈哈哈, 太骚了, 好了回归主题。还记得我之前提出的问题?
1.为什么dispacth 是匿名函数?
2. 为什么dispacth 一个action后, 还是返回action
问题1: 为什么dispatch 是是一个匿名函数 , 因为有的中间件原理的实现,并不会 next(action), 这时候需要肯定是增强的dispacth, redux-thunk 的执行原理, 就是当你传递一个函数, 直接调用这个函数,并把dispatch 的 权限 交给你自己处理。
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { // 这个的dispatch 其实是增强的dispatch, // 如果用store.dispatch如果还有其他中间件就丢失了 return action(dispatch, getState, extraArgument); } return next(action); }; } 复制代码
这里看下源码:
// 这个其实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)}const chain = middlewares.map(middleware => middleware(middlewareAPI))// compose 完之后将闭包更新成增强的dispatchdispatch = compose<typeof dispatch>(...chain)(store.dispatch) 复制代码
所以Redux 严格 意义上 并不算是洋葱模型, 他的洋葱模型是建立在你的每个中间件,都要next(action); 如果不 next(action) 其实就破坏了洋葱模型。
问题2: dispatch (action) 返回action, 就是为了方便下一个中间件的处理。