理解 redux 的middleware 中间件 机制: https://redux.js.org/advanced/middleware
假如对 store.dispatch 函数,做一些调整,比如 log、errorCatch 的事情,通常我们会想到 monkey-patch:
function addLog(store){
var next = store.dispatch;
store.dispatch = function(action){
console.log('action before');
next(action)
console.log('action after');
return action;
}
}
function catchError(store) {
var next = store.dispatch;
store.dispatch = function(action){
console.log('action before');
next(action)
console.log('action after');
return action;
}
}
addLog(store);
catchError(store);
看起来不错。但是,如果有多个 middleware需要处理,我们会考虑有一个 applayMiddleware 的函数,来帮助处理. 我们先把 middleware函数稍微修改一下,之前是直接赋值store.dispatch,我们可以直接返回新的dispatch 函数:
function addLog(store){
var next = store.dispatch;
return function(action){
console.log('action before');
next(action)
console.log('action after');
return action;
}
}
function catchError(store) {
var next = store.dispatch;
return function(action){
console.log('action before');
next(action)
console.log('action after');
return action;
}
}
如此一来,applayMiddleware 就可以拿到最新的 dispatch 函数,重新赋值 给 store.dispatch。
function applayMiddleware(store, fns){
var next = store.dispatch;
fns.forEach( (fn) => {
store.dispatch = fn(store);
})
}
来到这里,我们会想,既然每一次都要重新赋值 store.dispatch,何不干脆把store.dispatch 函数做为参数,传递给 middleware ?
function addLog(next){
return function(action){
console.log('action before');
next(action)
console.log('action after');
return action;
}
}
function catchError(next) {
return function(action){
console.log('action before');
next(action)
console.log('action after');
return action;
}
}
function applayMiddleware(store, fns){
var next = store.dispatch;
fns.forEach( (fn) => {
next = fn(next);
})
store.dispatch = next;
}
嗯嗯,事实上,来到这里,我们已经掌握了 middleware机制的诀窍:传递进来一个旧的函数,我包装一下,返回一个新的函数。
看一下我们的 middleware函数的结构:
function addLog(dispatch){
return function(action){
// xxxx
}
}
跟 redux 好像还差一层 ?人家是:
function addLog(store){
return function(dispatch){
return function(action) {
// xxxx
}
}
}
呃,为什么要再包装一层呢?因为redux 的 store还有 getState 等方法,中间件可能会需要,所以传递了 store,就把 middleware 再封装一层了。 因此,按照这种写法,我们的代码变成为:
function addLog(store){
return function(next){
return function(action){
console.log('action before');
next(action)
console.log('action after');
return action;
}
}
}
function catchError(store){
return function (next) {
return function(action){
try{
next(action)
}catch(e){
doSomeLog(e)
}
return action;
}
}
}
function applyMiddleware(store, fns){
var next = store.dispatch;
fns.forEach( (fn) => {
next = fn(store)(next); // 这里要执行2次。
});
store.dispatch = next;
}
这时候,我们的middle 函数也是:addLog = store => dispatch => action => { return dispatch(action)} 这种结构啦啦啦。 到此,我们其实已经完成 redux 的中间件核心思路了。 其它的,是 redux 在 函数调用技巧(比如:用一个compose 函数),和具体的实现了。
我们不妨看看 redux 的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)
}
// 这里先执行一次,就是最外层的 store 函数的执行。
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// chain 现在是2层结构了,传递 dispatch 函数给它
dispatch = compose(...chain)(store.dispatch)
// 最后,返回一个 "重写" 了dispatch 的 store。
return {
...store,
dispatch
}
}
}
再说一句,compose 函数,其实是自右向左的一个循环,每一个函数的返回(新的 dispatch),作为下一个函数的参数。————这是当然了,我们每一次都要重写 dispatch 嘛!
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
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)))
}