给事件总线类型标注 
这几天花了点时间写了个中国象棋对弈小网站, 今天在回顾代码的时候感觉现在用的事件总线太怪了, 一不小心就会写错.
ts
const ApiEvent = [
	'API:UN_AUTH',
	'API:NOT_FOUND',
	'API:LOGOUT',
	'API:FAIL',
	'API:LOGIN',
] as const;
const ChessEvent = [
	'MATCH:SUCCESS',
	'GAME:START',
	'GAME:END',
	'CHESS:MOVE',
	'CHESS:MOVE:END',
] as const;
type RequestCallback = (...args: any[]) => any;
type ResponseCallback = (...args: any[]) => any;
type Listener = (req: RequestCallback, resp: ResponseCallback) => void;
class EventEmitter<T extends readonly string[]> {
	private eventNames: T;
	listeners: Record<T[number], Set<Listener>> = {} as Record<
		T[number],
		Set<Listener>
	>;
	futureEvents: Record<
		T[number],
		{ req: RequestCallback; resp: ResponseCallback }[]
	> = {} as Record<
		T[number],
		{ req: RequestCallback; resp: ResponseCallback }[]
	>;
	constructor(EventNames: T) {
		this.eventNames = EventNames;
		this.eventNames.forEach((eventName) => {
			this.listeners[eventName as T[number]] = new Set<Listener>();
		});
	}
	on(eventName: T[number], listener: Listener) {
		if (!this.listeners[eventName]) {
			this.listeners[eventName] = new Set<Listener>();
		}
		this.listeners[eventName].add(listener);
		if (this.futureEvents[eventName]) {
			this.futureEvents[eventName].forEach(({ req, resp }) => {
				listener(req, resp);
			});
			delete this.futureEvents[eventName];
		}
	}
	off(eventName: T[number], listener: Listener) {
		if (!this.listeners[eventName]) return;
		this.listeners[eventName].delete(listener);
	}
	emit(
		eventName: T[number],
		req: RequestCallback = () => {},
		resp: ResponseCallback = () => {}
	) {
		this.listeners[eventName].forEach((listener) => listener(req, resp));
	}
	futureEmit(
		eventName: T[number],
		req: RequestCallback = () => {},
		resp: ResponseCallback = () => {}
	) {
		if (!this.futureEvents[eventName]) {
			this.futureEvents[eventName] = [];
		}
		this.futureEvents[eventName].push({ req, resp });
	}
}
const ApiBus = new EventEmitter(ApiEvent);
const GameBus = new EventEmitter(ChessEvent);
export { ApiBus, GameBus };这里虽然用了 ts, 但是我为了传递信息和获取响应, 让 listener 接收两个参数:一个是 req, 另一个是 resp.
req 是一个 get 函数, 调用它获取数据; resp 是回调函数, 参数类型未知. 现在的问题就在于, req 和 resp 都是没有类型标注的, 只有刚开始写才知道自己在写什么, 也加大了以后的维护难度. 现在我写的代码里只有一个生产者, 但是如果以后再写一个生产者的话, 没有类型标注就得到处翻类型, 就会很痛苦.
这里 ApiBus 基本不涉及回调函数, 主要是 GameBus.
使用 Channel 代替 EventBus 
在我的代码中, 我想的是 GameBus 主要用于 websocket 和棋盘的通信交互, 这里的未来事件也是用于 GameBus, 那在这里其实没有必要使用 EventBus, 而是采用 Channel.
Channel 与 EventBus 的异同 
- 同
- 都有生产者和消费者
- 异
- Channel 通常是一对一或者是多对一的关系(如果涉及并发操作, 也可以是多对多, 不过 js 是单线程), EventBus 一般是一对多. 
- Channel 在生产数据后, 如果没有对应的消费者来消费, 那么会将数据存起来而不是丢弃; EventBus 生产数据后一般直接广播而不是等待消费者来消费(未来事件除外) 
js
class Channel {
  constructor() {
    this.eventsQueue = {}
    this.listeners = {}
  }
  on(eventName, listener) {
    this.listeners[eventName] = listener
    while (this.eventsQueue[eventName]?.length) {
      const req = this.eventsQueue[eventName].shift()
      if (req === undefined) {
        return
      }
      listener(req)
    }
  }
  off(eventName) {
    if (!this.listeners[eventName])
      return
    delete this.listeners[eventName]
  }
  emit(eventName, req) {
    if (!this.listeners[eventName]) {
      this.eventsQueue[eventName].push(req)
      return
    }
    this.listeners[eventName](req)
  }
}
export default new Channel()简单用 js 写一下, 那接下来就是进行类型标注了. 虽然标题是给事件总线类型标注, 不过这里的方法是一样的.
首先定义每个事件以及对应的数据类型.
ts
type color = 'red' | 'black';
type position = { x: number; y: number };
interface GameEvents {
	'MATCH:SUCCESS': null;
	'GAME:START': {
		color: color;
	};
	'GAME:END': {
		winner: color;
	};
	'NET:GAME:START': {
		color: color;
	};
	'NET:GAME:END': {
		winner: color;
	};
	'NET:CHESS:MOVE': {
		from: position;
		to: position;
	};
	'NET:CHESS:MOVE:END': {
		from: position;
		to: position;
	};
}按实际调整. 接着给消息队列和 listeners 类型标注
ts
class Channel {
	private eventsQueue: {
		[K in keyof GameEvents]: Array<GameEvents[K]>;
	};
	private listeners: {
		[K in keyof GameEvents]?: Listener<GameEvents[K]>;
	} = {};
}on, off, emit
ts
class Channel {
	private eventsQueue: {
		[K in keyof GameEvents]: Array<GameEvents[K]>;
	};
	private listeners: {
		[K in keyof GameEvents]?: Listener<GameEvents[K]>;
	} = {};
	on<K extends keyof GameEvents>(
		eventName: K,
		listener: Listener<GameEvents[K]>
	) {
		if (!this.eventsQueue[eventName]) {
			this.eventsQueue[eventName] = [];
		}
		this.listeners[eventName] = listener as Listener<
			GameEvents[keyof GameEvents]
		>;
		while (this.eventsQueue[eventName]?.length) {
			const req = this.eventsQueue[eventName].shift();
			if (req === undefined) {
				continue;
			}
			listener(req);
		}
	}
	off(eventName: keyof GameEvents) {
		if (!this.listeners[eventName]) return;
		delete this.listeners[eventName];
	}
	emit<K extends keyof GameEvents>(eventName: K, req: GameEvents[K]) {
		if (!this.listeners[eventName]) {
			if (!this.eventsQueue[eventName]) {
				this.eventsQueue[eventName] = [];
			}
			this.eventsQueue[eventName].push(req);
			return;
		}
		this.listeners[eventName](req);
	}
}完整代码 
ts
type color = 'red' | 'black';
type position = { x: number; y: number };
interface GameEvents {
	'MATCH:SUCCESS': null;
	'GAME:START': {
		color: color;
	};
	'GAME:END': {
		winner: color;
	};
	'NET:GAME:START': {
		color: color;
	};
	'NET:GAME:END': {
		winner: color;
	};
	'NET:CHESS:MOVE': {
		from: position;
		to: position;
	};
	'NET:CHESS:MOVE:END': {
		from: position;
		to: position;
	};
}
type Listener<T> = (req: T) => void;
class Channel {
	private eventsQueue: {
		[K in keyof GameEvents]: Array<GameEvents[K]>;
	} = {} as {
		[K in keyof GameEvents]: Array<GameEvents[K]>;
	};
	private listeners: {
		[K in keyof GameEvents]?: Listener<GameEvents[K]>;
	} = {};
	on<K extends keyof GameEvents>(
		eventName: K,
		listener: Listener<GameEvents[K]>
	) {
		if (!this.eventsQueue[eventName]) {
			this.eventsQueue[eventName] = [];
		}
		this.listeners[eventName] = listener as Listener<
			GameEvents[keyof GameEvents]
		>;
		while (this.eventsQueue[eventName]?.length) {
			const req = this.eventsQueue[eventName].shift();
			if (req === undefined) {
				continue;
			}
			listener(req);
		}
	}
	off(eventName: keyof GameEvents) {
		if (!this.listeners[eventName]) return;
		delete this.listeners[eventName];
	}
	emit<K extends keyof GameEvents>(eventName: K, req: GameEvents[K]) {
		if (!this.listeners[eventName]) {
			if (!this.eventsQueue[eventName]) {
				this.eventsQueue[eventName] = [];
			}
			this.eventsQueue[eventName].push(req);
			return;
		}
		this.listeners[eventName](req);
	}
}
export default new Channel();如果有需要的话, 也可以按照这种方法给EventBus类型标注.