redux的中间件是在发出 Action之后,执行 Reducer 之前,添加的一些实现其他功能的函数。中间件的主要作用就是改造dispatch函数,使我们在发起action的时候可以实现一些其他业务逻辑。下面加入中间件创建一个store:
const logger = createLogger();
const store = createStore(
rootReducer,
initialStore,
applyMiddleware(thunk, logger),
);
在项目中一般会用到thunk和logger中间件,thunk的作用是可以发起异步action,logger的作用是在改变state前后打印state。创建store时通过调用applyMiddleware可以将两个中间件串起来依次执行。
createStore(reducer, preloadedState, enhancer),createStore的第三个函数是高阶函数,如果传了会返回enhancer(createStore)(reducer, preloadedState)。所以下面这种写法和上面相同:
const store = applyMiddleware(thunk,logger)(createStore)(rootReducer, initialStore);
接下来看一下具体是怎么使用中间件创建一个store的。
applyMiddleware
首先来看一下applyMiddleware,这个函数的作用是将所有中间件组成一个数组,依次执行。源码如下:
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.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
对应的ES5的源码如下,可能会比较容易理解
function applyMiddleware() {
for (var _len = arguments.length, middlewares = new Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
return function (createStore) {
return function () {
var store = createStore.apply(void 0, arguments);
var _dispatch = function dispatch() {
throw new Error("Dispatching while constructing your middleware is not allowed. " + "Other middleware would not be applied to this dispatch.");
};
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch() {
return _dispatch.apply(void 0, arguments);
}
};
var chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
});
_dispatch = compose.apply(void 0, chain)(store.dispatch);
return _objectSpread({}, store, {
dispatch: _dispatch
});
};
};
}
const store = applyMiddleware(thunk)(createStore)(rootReducer, initialStore)
调用 applyMiddleware函数创建store的时候先传了一个中间件thunk,执行applyMiddleware(thunk)返回一个函数,这个函数需要一个参数createStore,接着我们执行applyMiddleware(thunk)(createStore) 将createStore传进去,然后又返回了一个函数,我们传了参数(rootReducer, initialStore)。
参数都传进去之后看一下执行过程:
首先调用createStore(rootReducer, initialStore)创建了一个store,然后定义了一个dispatch(对应的ES5写法中的_dispatch)方法,函数做了一个错误处理。然后定义了一个middlewareAPI对象,里面有一个getState函数将store.getState赋给它,还有一个dispatch函数,调用时返回的是我们刚才定义的dispatch(_dispatch)函数。
然后执行
const chain = middlewares.map(middleware => middleware(middlewareAPI))
在调用applyMiddleware的时候只传了一个参数thunk进去,所以
const chain = [thunk(middlewareAPI)]
然后执行
dispatch = compose(...chain)(store.dispatch)
compose的作用是从右到左组合多个函数,比如: compose(f1,f2,f3) = (args) => f1(f2(f3(args))。因为这里只有一个函数,所以
dispatch = thunk(middlewareAPI)(store.dispatch)
最后返回改造过的dispatch方法。
return {
...store,
dispatch
}
redux-thunk
刚才的dispatch方法中用到了thunk中间件:
dispatch = thunk(middlewareAPI)(store.dispatch)
thunk是一个让dispatch可以处理异步action的中间件。
首先看一下redux-thunk的源码:
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;
从上面的代码可以看出
thunk = createThunkMiddleware() =
({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
thunk(middlewareAPI)(store.dispatch)在调用时首先传入了middlewareAPI,即传了({ dispatch, getState })。执行thunk(middlewareAPI)后,返回一个函数,这个函数需要一个参数next,对应传入的参数就是store.dispatch,然后又返回一个函数,所以
dispatch = action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
在项目中调用dispatch的时候会传一个action进去,如果传入的是函数,就执行这个函数,否则就执行next(action),即 store.dispatch(action)。
比如我传了下面这个action函数
function fetchHomeData(args) {
return (dispatch) => {
getHomeData(args).then((res) => {
dispatch({
type: actionTypes.FETCH_HOME_DATA,
homeData: res.data,
});
})
};
}
//调用
dispatch(fetchHomeData(args));
因为传入的是一个函数,会执行该函数;
dispatch(fetchHomeData(args)) = fetchHomeData(args)(dispatch, getState, extraArgument);
可以看到请求数据之后会再次发起一个dispatch
dispatch({
type: types.FETCH_HOME_DATA,
homeData: res.data,
});
这个dispatch对应的是applyMiddleware里面的dispatch方法,。
dispatch: (...args) => dispatch(...args)
// es5代码
dispatch: function dispatch() {
return _dispatch.apply(void 0, arguments);
}
在applyMiddleware函数里面又创建了一个dispatch函数,形成了一个闭包,所以在调用时返回的dispatch方法和最后通过中间件改变后的dispatch方法相同。
dispatch = compose(...chain)(store.dispatch)
// es5代码 上面的_dispatch和这个_dispatch相同
_dispatch = compose.apply(void 0, chain)(store.dispatch);
再次发起dispatch的时候又会调用一次thunk,这次传的action是一个对象,所以会执行next(action), 即直接执行store.dispatch(action),这次发起的dispatch是store中最原始的dispatch函数。
增加logger中间件
在一开始创建store的时候除了thunk中间件,还加入了logger中间件,用来打印state。
const store = applyMiddleware(thunk, logger)(createStore)(rootReducer, initialStore)
前面执行过程都是一样的,执行到这一行代码的时候有点不同
const chain = middlewares.map(middleware => middleware(middlewareAPI))
执行之后
const chain = [thunk(middlewareAPI), logger(middlewareAPI)]
接下来执行dispatch = compose(…chain)(store.dispatch),compose中会先执行logger(middlewareAPI)(store.dispatch),将执行的结果作为thunk(middlewareAPI)的参数再执行,得到:
dispatch = thunk(middlewareAPI)(logger(middlewareAPI)(store.dispatch))
先执行thunk函数,action还是传入之前的fetchHomeData函数,传入的是一个函数,所以会执行fetchHomeData函数。执行时又会发起一个dispatch,传入了一个action对象,接下来会执行next(action),这次对应的next是下一个中间件。所以会执行
logger(middlewareAPI)(store.dispatch)(action)
简化后的logger代码是这样的:
const logger = ({ getState }) => (next) => (action) => {
// ...打印当前state
returnedValue = next(action); // next(action) = store.dispatch(action)
// ...打印改变之后的state
return returnedValue;
};
在执行 next(action)的时候执行了redux原始的store.dispatch函数。
加入中间件后,最后都是会执行store.dispatch(action),只是改造的dispatch方法在执行过程中会加入一些其他作用。