前言
项目中一直使用redux来进行状态管理,对于redux中间件传递这一块一直不是很理解,只懂得使用不懂得原理,今天来分析一下applyMiddleware的源码。
加载中间件的加载
首先我们来看一下在项目中是如何加载中间件的
import { createStore, applyMiddleware } from 'redux';
imoprt thunk from 'redunx-thunk';
import todos from './reducers';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(todos,[ 'Use Redux' ]);
如果你用过redux就会知道,加载中间件会使用到redux提供的工具方法:applyMiddleware,将你需要使用的中间件作为参数传递给applyMiddleware,它会返回一个函数,然后再将createStore传递进,最后得到一个新的createStore;
下面我们来看一下applyMiddleware的源码(可通过gitHub获取)
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.`
)
}
let chain = [] //用于存放中间件
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
代码非常少,20行左右,下面我们就开始分析代码吧
createStore => (...args) =>{....}
我们知道applyMiddleware返回的是一个函数,从使用的方法可以看到这里createStore,就是我们一开始传递进去的那个createStore,它又返回一个函数,这个就是上面说的createStoreWithMiddleware,简单来说,最后调用的是
(reducer,initState)=>{
const store = createStore(reducer,initState);
}
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
let chain = []
这几行注释已经说得很清楚了,就不细说了
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
这里声明了一个变量middlewareAPI,它是一个对象,有两个方法(从这里开始非常的绕,我已经有点晕了。使用了大量的高阶函数)
1.getState 这个方法是对store中的getStat方法的引用
2.dispatch 从上面可以看到已经定义了一个dispatch,规定不允许在加载中间时调用,在下面又重新修改了指向。(表示对store.dispatch的封装)
chain = middlewares.map(middleware => middleware(middlewareAPI));
这行代码对传递进行的中间件数组进行一次map,map中调用中间件,并将middlewareAPI作为参数传递给中间件,这里我们就拿redux-thunk来看看中间件是长什么样子的
export default function thunkMiddleware({ dispatch, getState }) {
return next => action =>
typeof action === 'function' ?
action(dispatch, getState) :
next(action);
}
我们通常使用redux-thunk来进行ajax操作,在上面看到进行map操作时将middlewareAPI传递到了redux-thunk,然后它返回了一个函数,用es5表示就是下面这样子的
funciton thunkMiddleware(middlewareAPI){
return function(next){
return function(action){
typeof action === 'function' ?
action(dispatch, getState) :
next(action);
}
}
}
我们在回到上面
chain = middlewares.map(middleware => middleware(middlewareAPI))
那么现在我们可以知道,这个东西返回了一个像这样的函数
function(next){
return function(action){
typeof action === 'function' ?
action(dispatch, getState) :
next(action);
}
}
在接着往下看之前
dispatch = compose(...chain)(store.dispatch);
在说这里之前,我们先看一下compose的源码
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
之前说到chain是一个组数,那么chain === funcs,这个方法首先判断chain数组的长度,如果为0就就返回一个什么都不做的函数(为0就意味着没有中间件),如果直接一个中间件就返回这个中间件并调用它,并且将store.dispatch传递进去,并赋值给dispatch
return funcs.reduce((a, b) => (...args) => a(b(...args))
我们重点来看这行代码,reduce是数组的的一个方法,它的第一个参数是一个函数,这个函数接受4个参数,分别是previousValu(上一次的值),currentValue(当前值),currentIndex(当前值的索引),array;reduce 为数组中的每一个元素依次执行回调函数(不包括数组中被删除或从未被赋值的元素)。
这里的args就是外面传递的store.dispatch;这段代码写成es5的形式大概是下面这样(...agrs是es6语法,如果有不清楚的请参阅# ECMAScript 6 入门)
funcs.reduce(function(a,b){
retrurn function(... args]){
return a(b(... args))
}
})
从代码中可以看出(注意两个...ags,代表的含义不同,第一个代表的是剩余参数,第二个agrs代表的是展开数组),对每个元素执行了回调函数,它又再次返回了一个新的函数(真的无力,嵌套太深了)
return a(b(... args))
继续来看这里,这里是依次执行了中间件,并将返回的函数传递给下一个中间件
如果有数组[fn1,fn2,fn3,fn4],那么返回 fn1(fn2(fn3(fn4(..args))))
还记得thunk的代码吗,这里我们还是拿thunk来举例,看下面代码
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
如果只有一个中间件,那么这里的next表示的是store.dispatch,否则表示其它的中间件。
总的来说,如果我们只使用一个中间,那么最后得到的dispatch方法就是
action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};