基于ts泛型实现的提示友好型事件管理器

该事件管理器,利用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的作用

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的友好提示,示例如下:
基于ts泛型实现的提示友好型事件管理器_第1张图片
这里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的作用

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
其它类函数都大同小异。这里就不做重复说明了。

使用

使用事件管理器,即可以用继承的方式,也可以用组合的方式。为了更符合设计原则。推荐使用组合的方式来利用事件管理器。
如下例子:
基于ts泛型实现的提示友好型事件管理器_第2张图片
当被攻击的时候,可以通过emit发送事件。
基于ts泛型实现的提示友好型事件管理器_第3张图片
目前,这个事件管理器比较适用于我的小型项目。可以借鉴使用哈。

源码:

/**
 * @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();
    }
}

最后,祝愿各位程序员同行可以砥砺前行,早日实现精神和财富双自由!

你可能感兴趣的:(js成长日记,vscode,前端,typescript)