该事件管理器,利用vscode的智能提示系统和ts泛型。实现了如下功能:
ts泛型,是类型函数
正常我们编写的函数像这样:
function sayHello(para:string): void {
console.log('hello ' + para);
}
sayHello('world');
小括号内的内容就是该sayHello函数需要传递的参数。
在大括号内可以引用这个参数编写逻辑。
然后在其它地方调用的时候,传入符合string类型的参数就可以了。
上述例子最后控制台打印:hello world
如果用到了泛型,就是用尖括号<>对应小括号。在尖括号内编写参数。
举个例子:
function sayHello<T>(para:T): void{
console.log('hello ' + para);
}
sayHello('world');
这个例子只是简单的用了一下泛型,其实并没有起到实际性的作用。
因为我们用泛型最重要的功能就行进行类型提示和限制。
extends就像ES6里的类extends,用来表示子类继承于父类。
比如我们想要让上述sayHello函数的参数只能限制和几个小朋友说你好。
可以这样编写:
function sayHello
<T extends "xiaoMing" | "xiaoHong" | "xiaoFang">
(para: T): void {
console.log("hello " + para);
}
sayHello('pangHu');//VSCODE错误提示:类型“"pangHU"”的参数不能赋给类型“"xiaoMing" | "xiaoHong" | "xiaoFang"”的参数
当我们调用sayHello的时候,vscode就可以给到我们符合类型T的友好提示,示例如下:
这里T就属于"xiaoMing" | “xiaoHong” | "xiaoFang"类型的子类。在调用的时候,只能选择这三者作为参数传递。
泛型不仅可以用在函数上,还可以用在类上,这样可以做更丰富的类型限制和提示功能。
本文事件管理器的核心也就是在类上用的泛型。
类上用泛型,举一个简单的例子:
class SayManager<T> {
public sayHello<E extends T>(para: E) {
console.log("hello " + para);
}
public sayBye<E extends T>(para: E) {
console.log("bye " + para);
}
}
const sayManager = new SayManager<"xiaoMing" | "xiaoHong" | "xiaoFang">();
sayManager.sayBye("xiaoMing");
这个例子和上面对函数进行参数范围限制类似。只是将要限制的类型提升到了类上。然后类里的所有函数都可以利用这个泛型进行类型限制。
keyof用来提取对象类型的键字符串。有点类似于Object.getOwnPropertyNames函数
比如:
type Keys = {
hello: string;
world: number;
};
function use<T extends keyof Keys>(para: T) {}
use('hello');
这里,use函数的参数para被限制到了Keys的键。类型Keys的键包括hello和world。所以use函数调用的时候,只能选择这两者。
简单说了一下泛型的技术点。这些已经足够用来开发一个类型提示友好的事件管理器了。
首先我们定义一个事件管理器类,让其接受一个泛型对象来约束可以订阅的内容。
interface EType {
[key: string]: (...args: any[]) => any;
}
export class EventManager<E extends EType> {}
然后添加订阅函数on,使其利用泛型变量E来约束可以订阅的内容
interface EType {
[key: string]: (...args: any[]) => any;
}
export class EventManager<E extends EType> {
private eventMap: Map<keyof E, eventObjList> = new Map();
public on<K extends keyof E>(eventName: K, cb: (...args: Parameters<E[K]>) => any, target: object) {
const obj: eventObj = {
cb,
target,
isOnce: false
};
this.register(eventName, obj);
}
}
其中Parameters是TS自带的泛型函数,用于获取函数类型的参数类型。
E[K]指向的是(…args: any[]) => any
其它类函数都大同小异。这里就不做重复说明了。
使用事件管理器,即可以用继承的方式,也可以用组合的方式。为了更符合设计原则。推荐使用组合的方式来利用事件管理器。
如下例子:
当被攻击的时候,可以通过emit发送事件。
目前,这个事件管理器比较适用于我的小型项目。可以借鉴使用哈。
/**
* @author ccbbs
* @example
* ```ts
* type GameEventEmitParaType = {
* FINISH_GAME: (isSuccess: boolean) => void,
* GET_SCORE: (...score: number[]) => void
* }
* const someEventManager = new EventManager();
* someEventManager.emit('FINISH_GAME', true);
* someEventManager.emit('GET_SCORE', 1, 2, 3);
* ```
*/
type eventObj = {
cb: Function;
target: object;
isOnce: boolean;
};
type eventObjList = {
[key: number]: eventObj;
};
interface EType {
[key: string]: (...args: any[]) => any;
}
export class EventManager<E extends EType> {
private eventObjAmount: number = 0;
private eventMap: Map<keyof E, eventObjList> = new Map();
public on<K extends keyof E>(eventName: K, cb: (...args: Parameters<E[K]>) => any, target: object) {
const obj: eventObj = {
cb,
target,
isOnce: false
};
this.register(eventName, obj);
}
public once<K extends keyof E>(eventName: K, cb: (...args: Parameters<E[K]>) => any, target: object) {
const obj: eventObj = {
cb,
target,
isOnce: true
};
this.register(eventName, obj);
}
private register<K extends keyof E>(eventName: K, obj: eventObj) {
const eventObjList = this.eventMap.get(eventName) || {};
if (Object.getOwnPropertyNames(eventObjList).length === 0) this.eventMap.set(eventName, eventObjList);
for (let i in eventObjList) {
if (eventObjList[i].cb === obj.cb && eventObjList[i].target === obj.target) {
return;
}
}
eventObjList[this.eventObjAmount++] = obj;
}
public offTarget(targetIn: object) {
this.eventMap.forEach((eventObjList, eventName) => {
for (let i in eventObjList) {
const { target } = eventObjList[i];
if (target === targetIn) {
delete eventObjList[i];
}
}
this.judgeEventEmputy(eventName);
});
}
public off<K extends keyof E>(eventName: K, cb: (...args: Parameters<E[K]>) => void, target: object) {
const eventObjList = this.eventMap.get(eventName);
if (eventObjList) {
for (let i in eventObjList) {
if (eventObjList[i].cb === cb && eventObjList[i].target === target) {
delete eventObjList[i];
break;
}
}
}
this.judgeEventEmputy(eventName);
}
public emit<K extends keyof E>(eventName: K, ...arg: Parameters<E[K]>) {
const eventObjList = this.eventMap.get(eventName);
if (eventObjList) {
for (let i in eventObjList) {
const { cb, isOnce, target } = eventObjList[i];
cb.call(target, ...arg);
if (isOnce) delete eventObjList[i];
}
}
this.judgeEventEmputy(eventName);
}
private judgeEventEmputy<K extends keyof E>(eventName: K) {
const eventObjList = this.eventMap.get(eventName);
if (eventObjList && Object.getOwnPropertyNames(eventObjList).length === 0) {
this.eventMap.delete(eventName);
}
}
public clear() {
this.eventMap.clear();
}
}
最后,祝愿各位程序员同行可以砥砺前行,早日实现精神和财富双自由!