store
的dispatch
进行重新和包装,修改store.dispatch
的默认行为;redux中间件是对redux功能的一种扩展,也是扩展dispatch的唯一标准方式;store.dispatch
的重新,那么如果不计代码整洁性及冗杂性,任意的middleware
均可以手写实现;假如我们需要在store.dispatch
前后需要查看对应的action
以及state
,我们需要这么实现:
let action = addTodo('learning redux middlewares');
console.log('dispatched action: ', action);
store.dispacth(action);
console.log('nextState: ', store.getState());
如果上述功能是为了我们方便查看日志,显然将其封装为一个函数更方便;
function dispatchAndLog(store, action) {
console.log('dispatched action: ', action);
store.dispacth(action);
console.log('nextState: ', store.getState());
}
这样相对方便了,但是每次使用我们需要引入这个函数;如果我们还想偷懒,可以通过调用原生的store.dispatch
就可以实现这些功能岂不是美滋滋~
于是,我们开始重新`store.dispatch
const next = store.dispatch;
store.dispatch = function (action) {
console.log('dispatched action: ', action);
let result = next(action);
console.log('next state: ', store.getState());
return result;
}
参考redux的源码我们知道,store.dispatch
函数接受一个action
参数,返回同一个dispacthed action
;因此,我们需要对该段代码继续优化;
function dispatchAndLogWrapper (store) {
const next = store.dispatch;
return function dispatchAndLog(action) {
console.log('dispatched action: ', action);
let result = next(action);
console.log('nextState: ', store.getState());
return result;
}
}
这样,我们中间件返回一个被middleware
重写后的store.dispatch
函数;这样奠定了链式调用的基础;
但是对于链式调用,后一个中间件对store.dispatch
的修改是基于前一个中间件修改的基础上进行的;而上述实现中我们的next
总是为固定值,即原生的store.dispatch
;next
的期望值应当是上一个中间件重写后的store.dispatch
;因此,中间件的实现中next通常作为参数传入;next
的初始值取原生的store.dispatch
;
中间件函数的形式描述为:({ getState, dispatch }) => next => action
;
据此,改写我们的模拟中间件实现:
const dispatchAndLogWrapper = store => next => action => {
console.log('dispatch: ', action);
let result = next(action);
console.log('next state: ', store.getState())
return result;
}
模拟实现applyMiddleware
applyMiddleware
可以实现对中间件的链式调用,
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse(); // applyMiddleware其实是store Enhancer扩展机制的一个范例,因此也遵循compose从右至左的组合方式;
let dispatch = store.dipatch; // 获取初始的dispatch
// 这里next的初始值为store.dispatch,每次调用一个middleware都是在上一个middleware返回的dispatch的基础进行修改;
middlewares.map(middleware => {
dispatch = middleware(store)(dispatch); // 中间件函数的形式描述为:`({ getState, dispatch }) => next => action`
})
reurn {
...store,
dispatch
}; // 将dispatch的修改应用到store并返回修改后的store
}
// source code
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;
使用方法:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
// Note: this API requires redux@>=3.1.0
const store = createStore(rootReducer, applyMiddleware(thunk));
分析:
首先可以看到,其实redux-thunk
对我们模拟的中间件外部多了一层封装,用于接受外部传入的其他参数;导出的是函数createThunkMiddleware
的执行结果,即返回结果与我们的模拟结构类似;
其次,next
的来源不同;redux-thunk
实现中,next
是作为参数传入的而不是直接赋值为store.dispatch
;
另外,内部实现对action
的类型进行了判断并分别进行了处理:是对象还是Action Creator
函数;
// 函数形式描述为:(...middlewares)=>createStore=>(...args)
function applyMiddleware (...middlewares) {
return createStore => (...args) => { // 返回应用了中间件的一个store Enhancer
const store = createStore(...args);
const dispatch = store.dispatch;
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 这里其实把middlewares.map(middleware => {dispatch = middleware({getState, dispatch})(dispatch)})分为了两步,chain是一个数组,数组的成员为每个调用了middlewareAPI的中间件
const chain= middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
}
}
}