Skip to content

发布订阅模式

js
class EventEmitter {
	constructor() {
		this.cache = {};
	}

	on(name, fn) {
		if (this.cache[name]) {
			this.cache[name].push(fn);
		} else {
			this.cache[name] = [fn];
		}
	}

	off(name, fn) {
		const tasks = this.cache[name];
		if (tasks) {
			const index = tasks.findIndex((f) => f === fn || f.callback === fn);
			if (index >= 0) {
				tasks.splice(index, 1);
			}
		}
	}

	emit(name, once = false) {
		if (this.cache[name]) {
			// 创建副本,如果回调函数内继续注册相同事件,会造成死循环
			const tasks = this.cache[name].slice();
			for (let fn of tasks) {
				fn();
			}
			if (once) {
				delete this.cache[name];
			}
		}
	}
}

// 测试
const eventBus = new EventEmitter();
const task1 = () => {
	console.log("task1");
};
const task2 = () => {
	console.log("task2");
};

eventBus.on("task", task1);
eventBus.on("task", task2);
eventBus.off("task", task1);
setTimeout(() => {
	eventBus.emit("task"); // task2
}, 1000);

使用发布订阅模式解耦

在前端项目开发中,会遇到如router、axios等模块发生一些事件需要触发页面或其它模块的操作,但直接互相引用往往会使得组件之间的耦合性越来越大,逐渐堆积成屎山,因此可以在设计上采用发布订阅模式对组件进行解耦

ts
const eventNames = ['API:UN_AUTH', 'API:VALIDATE_ERROR'] as const;
type EventNames = (typeof eventNames)[number]

class EventEmitter {
    private listeners: Record<EventNames, Set<Function>> = {
        'API:UN_AUTH': new Set(),
        'API:VALIDATE_ERROR': new Set(),
    };

    on(eventName: EventNames, callback: Function) {
        this.listeners[eventName].add(callback);
    }

    emit(eventName: EventNames, ...args: any[]) {
        this.listeners[eventName].forEach((callback) => callback(...args));
    }
}

export default new EventEmitter();

此时如在某个axios拦截其中

js
const sucessHandler = () => {
    // ...
}

const errorHandler = () => {
    if (error.response.status === 401) {
        eventEmitter.emit('API:UN_AUTH');
    }
}

axios.intercepts.response.use(sucessHandler, errorHandler);

路由中

js
eventEmitter.on('API:UN_AUTH', () => {
    router.push('/login');  // 跳转到登录页
})