理解 Redux 的中间件
将该思想抽象出来,其实和 Redux 就无关了。问题变成,怎样实现在截获函数的执行,以在其执行前后添加自己的逻辑。 为了演示,我们准备如下的示例代码来模拟 Redux dispatch action 的场景: const store = {
dispatch: action => {
console.log("dispating action:", action);
}
};
store.dispatch({ type: "FOO" });
store.dispatch({ type: "BAR" });
我们最终需要实现的效果是 Redux 中 以打日志为例,我们想在调用 dispatch 时进行日志输出。 尝试1 - 手动直接的做法就是手动进行。 console.log("before dispatch `FOO`");
store.dispatch({ type: "FOO" });
console.log("before dispatch `FOO`");
console.log("before dispatch `BAR`");
store.dispatch({ type: "BAR" });
console.log("before dispatch `BAR`");
但其实这并不算一个系统的解决方案,至少需要摆脱手动这种方式。 尝试2 - 包装既然所有 dispatch 操作都会打日志,完全有理由抽取一个方法,将 dispatch 进行包装,在这个方法里来做这些事情。 function dispatchWithLog(action) {
console.log(`before dispatch ${action.type}`);
store.dispatch(action);
console.log(`after dispatch ${action.type}`);
}
但调用的地方也得变,不能直接使用原始的 - store.dispatch({ type: "FOO" });
- store.dispatch({ type: "BAR" });
+ dispatchWithLog({ type: "FOO" });
+ dispatchWithLog({ type: "BAR" });
尝试3 - 替换实现/Monkeypatching如果我们直接替换掉原始函数的实现,便可以做到调用的地方不受影响而实现新增的 log 功能,虽然修改别人提供的方法容易引起 bug 且不太科学。 const original = store.dispatch;
store.dispatch = function log(action) {
console.log(`before dispatch ${action.type}`);
original(action);
console.log(`after dispatch ${action.type}`);
};
store.dispatch({ type: "FOO" });
store.dispatch({ type: "BAR" });
尝试4 - 多个函数的截获除了添加 log,如果还想对每次 dispatch 进行错误监控,只需要拿到前面已经替换过实现的 dispatch 方法再次进行替换包装即可。 const original = store.dispatch;
store.dispatch = function log(action) {
console.log(`before dispatch ${action.type}`);
original(action);
console.log(`after dispatch ${action.type}`);
};
const next = store.dispatch;
store.dispatch = function report(action) {
console.log("report middleware");
try {
next(action);
} catch (error) {
console.log(`error while dispatching ${action.type}`);
}
};
所以针对单个功能的中间件,我们可以提取出其大概的样子来了: function middleware(store) {
const next = store.dispatch;
store.dispatch = function(action) {
// 中间件中其他逻辑
next(action);
// 中间件中其他逻辑
};
}
改写日志和错误监控为如下: function log(store) {
const next = store.dispatch;
store.dispatch = function(action) {
console.log(`before dispatch ${action.type}`);
next(action);
console.log(`after dispatch ${action.type}`);
};
}
function report(store) {
const next = store.dispatch;
store.dispatch = function(action) {
console.log("report middleware");
try {
next(action);
} catch (error) {
console.log(`error while dispatching ${action.type}`);
}
};
}
然后按需要应用上述中间件即可: log(store);
report(store);
上面中间件的调用可专门编写一个方法来做: function applyMiddlewares(store, middlewares) {
middlewares.forEach(middleware => middleware(store));
}
隐藏 Monkeypatching真实场景下,各中间件由三方编写,如果每个中间件都直接去篡改 所以中间件的模式更新成如下: function middleware(store) {
const next = store.dispatch;
- store.dispatch = function(action) {
+ return function(action) {
// 中间件中其他逻辑
next(action);
// 中间件中其他逻辑
};
}
改写 function log(store) {
const next = store.dispatch;
- store.dispatch = function(action) {
+ return function(action) {
console.log(`before dispatch ${action.type}`);
next(action);
console.log(`after dispatch ${action.type}`);
};
}
function report(store) {
const next = store.dispatch;
- store.dispatch = function(action) {
+ return function(action) {
console.log("report middleware");
try {
next(action);
} catch (error) {
console.log(`error while dispatching ${action.type}`);
}
};
}
更新 function applyMiddlewares(store, middlewares) {
middlewares.forEach(middleware => {
store.dispatch = middleware(store);
});
}
最后,应用中间件: applyMiddlewares(store, [log, report]);
进一步优化之所以在应用中间件过程中每次都重新给 如果中间件中不是直接从 function applyMiddlewares(store, middlewares) {
store.dispatch = middlewares.reduce(
(next, middleware) => middleware(next),
store.dispatch
);
}
忽略掉实际源码中的一些差异,以上,大致就是 Redux 中间件的创建和应用了。 测试function m1(next) {
return function(action) {
console.log(`1 start`);
next(action);
console.log(`1 end`);
};
}
function m2(next) {
return function(action) {
console.log(`2 start`);
next(action);
console.log(`2 end`);
};
}
function m3(next) {
return function(action) {
console.log(`3 start`);
next(action);
console.log(`3 end`);
};
applyMiddlewares(store, [m1, m2, m3]);
store.dispatch({ type: "FOO" });
store.dispatch({ type: "BAR" });
}
输出结果: 3 start
2 start
1 start
dispating action: { type: 'FOO' }
1 end
2 end
3 end
3 start
2 start
1 start
dispating action: { type: 'BAR' }
1 end
2 end
3 end
相关资源 |