问题背景:
随着项目功能不断迭代,公共方法或通用组件内部的判断逻辑会越来越多,导致代码臃肿、后期维护成本急剧上升。
典型场景:网络请求返回统一结果(如登录失效、参数错误),但不同页面需要执行不同的业务逻辑。如果将所有页面逻辑都耦合在公共方法中,会让公共模块变得庞大且难以维护。
传统方式:
网络请求:
service.interceptors.response.use(
async (res) => {
if(res.headers['set-cookie']){
cookieHeader = res.headers['set-cookie']
}
return res.data;
},
(err) => {
return Promise.reject(err);
}
);
业务功能:
- A 页面调用接口,把结果存到 **Pinia/Vuex** 或 **本地存储**
- B 页面 **监听状态变化**,才能做处理
现在的解决方法:
采用【事件总线作为中间件】的设计方案,通过发布订阅模式解耦模块。让各业务页面在自身模块内独立监听、处理事件,既不侵入公共逻辑,也不互相影响,从而显著降低系统耦合度,提升代码可维护性。
代码实现:
定义中间件:
const EVENT_NAMES = ["API:UN_AUTH", "API:VALIDATE_ERROR"] as const
type EventName = (typeof EVENT_NAMES[number])
class EventEmitter {
private listeners: Record<EventName, Set<Function>>;
constructor() {
this.listeners = EVENT_NAMES.reduce((acc, eventName) => {
acc[eventName] = new Set();
return acc;
}, {} as Record<EventName, Set<Function>>);
}
on(eventName: EventName, listener: Function) {
this.listeners[eventName].add(listener);
}
emit(eventName: EventName, ...args: any[]) {
this.listeners[eventName].forEach((listener) => listener(...args));
}
off(eventName: EventName, listener: Function) {
this.listeners[eventName].delete(listener);
}
clear(eventName: EventName) {
this.listeners[eventName].clear();
}
}
export default new EventEmitter()
页面/组件监听事件处理【回调函数】:
eventEmitter.on("API:UN_AUTH", (res) => {
console.log("页面的总线", res)
})
网络请求返回结果处:【以下代码只是模拟使用】
if(response.code === 401){
eventEmitter.emit('API:UN_AUTH', { message: '未授权'})
}
else if(response.code === 404){
eventEmitter.emit('API:VALIDATE_ERROR', { message: '校验失败'})
}
传统方案和事件总线方案的区别:
1、传统方案
- 耦合度较高,一处修改可能牵连其他模块;
- 网络请求返回的数据,必须在**调用处处理**,如需在其他位置使用,必须先存入状态管理或本地存储;
- 遵循 “谁调用接口,谁处理错误” 的模式,错误只回传到调用方。
2、事件总线方案
- 耦合度更低,修改 A 模块逻辑不会影响其他模块;
- 不需要中转存储,一处触发事件,所有关心该事件的地方都能直接接收数据;
- 遵循 “谁关心这个事件 / 错误,谁就监听处理”,实现真正的解耦与分布式处理。