简体   繁体   中英

Extending event definitions - Interface incorrectly implements interface

I'm trying to enforce event definitions in my code using an interface and class of the same name as in the following example:

export declare interface ClientEvents {
    on(event: "event_name", data: (data: string) => void) : this;
    emit(event: "event_name", data: string) : boolean;
}

export class ClientEvents extends EventEmitter {}

This works well on its own for IDE completion and ensuring that only those events are broadcast by the event object.

The problem comes when I attempt to extend this interface, as below:

export declare interface SpecificClientEvents extends ClientEvents {
    on(event: "child_event_name", data: (data: string) => void) : this;
    emit(event: "child_event_name", data: string) : boolean;
}

export class SpecificClientEvents extends ClientEvents {}

I get the following errors:

TS2430: Interface 'SpecificClientEvents' incorrectly extends interface 'ClientEvents'. for the interface

TS2415: Class 'SpecificClientEvents' incorrectly extends base class 'ClientEvents'. for the class

I have also attempted to separate the class and interface into separate definitions as per the below example:

export interface ClientEventDefinitions {
    on(event: "event_name", data: (data: string) => void) : this;
    emit(event: "event_name", data: string) : boolean;
}

export class ClientEvents extends EventEmitter implements ClientEventDefinitions {}

export interface SpecificClientEventDefinitions {
    on(event: "child_event_name", data: (data: string) => void) : this;
    emit(event: "child_event_name", data: string) : boolean;
}

export class SpecificClientEvents extends ClientEvents implements SpecificClientEventDefinitions {}

This compiles OK but my IDE doesn't detect the event names like it does when the class and interface have the same name. Is this likely a problem with the IDE? This doesn't work for both the parent and child classes.

I'm also able to execute new SpecificClientEvents().emit("an event that isn't defined"); without any errors using the 2nd example.

EDIT:

If I manually add the methods from the parent interface to the child interface, this works absolutely fine. But what's the point of extending the parent in the first place if I need to add the methods manually?

EDIT 2:

I opted to use the strict event emitter package with the following code to meet my needs

export type ClientEventEmitter = StrictEventEmitter<EventEmitter, ClientEvents>;

export interface ClientEvents {
    event_name: (data: string) => void;
}

export type SpecificClientEventsEmitter = StrictEventEmitter<EventEmitter, SpecificClientEvents>;

export interface SpecificClientEvents extends ClientEvents {
    child_event_name: (data: string) => void;
}

I think you may not be aware that you are using a feature called string literal types , which means your two interfaces have incompatible types and your first example has the expected behaviour.

Here is a simplified version for clarity:

interface A {
  prop: 'String literal type';
}

interface B extends A {
  prop: 'Another string literal type';
}

This will give you an error message saying: Type '"Another string literal type"' is not assignable to type '"String literal type"'

I'm also able to execute new SpecificClientEvents().emit("an event that isn't defined"); without any errors using the 2nd example.

This seems to be because implements is not enforcing the string literal type from SpecificClientEventDefinitions and the SpecificClientEvents class is instead using the types from EventEmitter ( string | symbol ).

Here is an alternative approach using a generic class which I think solves your problem:

export class ClientEvents<T extends string> extends EventEmitter {
  on(event: T, data: (data: string) => void): this {
    return super.on(event, data);
  }
  emit(event: T, data: string): boolean {
    return super.emit(event, data);
  }
}

With this code when you create a new emitter with new ClientEvents<'special_event_name'>() , the on and emit functions will require events with the 'special_event_name' type.

This answer is best what you can get for your exact needs, but if you need a more complex mapping between event name and it's data type, you can use something like this

export interface ClientEvents<T> {
    on(event: keyof T, data: (d: T[typeof event]) => void) : this;
    emit(event: keyof T, data: T[typeof event]) : boolean;
}

type SpecificEvents = {
    eventName1: string,
    eventName2: number
}

class SpecificClientEvents implements ClientEvents<SpecificEvents> {
    on(event: keyof SpecificEvents, data: (d: SpecificEvents[typeof event]) => void) : this {
        switch (event) {
            case "eventName1":
                // compiler forces you to pass string
                data("string value");
                break;
            case "eventName2":
                // compiler forces you to pass number
                data(2);
                break;
            default:
                break;
        }

        return this;
    }

    emit(event: keyof SpecificEvents, data: SpecificEvents[typeof event]): boolean {
        switch (event) {
            case "eventName1":
                // data is of type string
                break;
            case "eventName2":
                // data is of type number
                break;
            default:
                break;
        }

        return true;
    }
}

or a simpler inline type where most IDE's will autocomplete implementation and correct argument types.

class SimpleEvents implements ClientEvents<{ eventName: number }> {
    on(event: "eventName", data: (d: number) => void): this {
        throw new Error("Method not implemented.");
    }

    emit(event: "eventName", data: number): boolean {
        throw new Error("Method not implemented.");
    }
}

Here is the link to the playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM