联合类型的Typesafe事件处理程序

时间:2017-04-14 19:12:14

标签: events typescript union-types

我正在尝试使用union类型构建一个轻量级事件系统,我可以在其中侦听union中一种类型的事件。这是我到目前为止(不幸的是,它并没有真正使用类型):

class EventSystem {
    events: { [key: string]: { (event: EventType) }[] };

    constructor() {
        this.events = {};
    }

    emit(key: string, event: EventType): void {
        var arr = this.events[key];
        for (let i = 0; i < arr.length; ++i) {
            arr[i](event);
        }
    }

    on(key: string, callback: (event: EventType) => void) {
        if (key in this.events) {
            this.events[key].push(callback);
        } else {
            this.events[key] = [callback];
        }
    }
}

interface EventA {
    foo: Number
}

interface EventB  {
    bar: Number
    baz: string
}

type EventType = EventA | EventB

const EventNames = {
    EventA: 'EventA',
    EventB: 'EventB'
}

let x = {foo: 2} as EventA;
let y = {
    bar: 4,
    baz: "test"
} as EventB;


let es = new EventSystem();

es.on(EventNames.EventA, function (a: EventA) {
    console.log(a);
});

//Triggers the on above
es.emit(EventNames.EventA, x);

//Unfortunately, this also triggers the on above
es.emit(EventNames.EventA, y);

我真正想要的是这样的:

let es = new EventSystem<EventType>();

es.on<EventA>(function (a) {
    //a is inferred to be EventA
    console.log(a);
});

//Triggers the on above
es.emit(x);

//Will not trigger the on, since the type does not match
es.emit(y);

//Type error, since number is not in EventType
es.emit(4);

在Typescript中可以这样吗?如果没有,是否有比我正在做的更安全的方法?或者更好的方式来获得这种行为?

修改

目前,我正在做以下事情。它为EventSystem类添加了很多样板(我有数百种消息类型),并且在我看来也使api有点难看,但至少我得到类型安全。重复代码的数量让我觉得必须有更好的方法。

class EventSystem {
    events: {[P in EventNames]: { (event: EventType) }[]} = {
        'EventA': [],
        'EventB': []
    };

    emitEventA(event: EventA): void {
        this.events['EventA'].forEach((eventFunc) => eventFunc(event));
    }

    emitEventB(event: EventB): void {
        this.events['EventB'].forEach((eventFunc) => eventFunc(event));
    }

    onEventA(callback: (event: EventA) => void) {
        this.events['EventA'].push(callback);
    }

    onEventB(callback: (event: EventB) => void) {
        this.events['EventB'].push(callback);
    }
}

interface EventA {
    foo: Number
}

interface EventB {
    bar: Number
    baz: string
}

type EventType = EventA | EventB
type EventNames = 'EventA' | 'EventB'

let x = { foo: 2 } as EventA;
let y = {
    bar: 4,
    baz: "test"
} as EventB;


let es = new EventSystem();

es.onEventA(function (a) {
    console.log(a);
});

//Triggers the on above
es.emitEventA(x);

//Correctly caught now
es.emitEventA(y);

1 个答案:

答案 0 :(得分:0)

是的,有可能。以下是一些适用于我的项目的示例代码:

class ProducerClass {
    private config;
    private logger;
    constructor(config: Config, logger: Logger);
    enqueue<T extends string, M extends object>(topic: T, message: M): Promise<boolean>;
    connect(): void;
}

const producer = ProducerClass(config, logger);

type Topic = 'someTask' | 'someOtherTask';

interface Message {
    objectId: number;
}

await producer.enqueue<Topic, Message>('someTask', { objectId: id });