node.js - Deklarera händelser i en TypeScript-klass som utökar EventEmitter


Jag har en klass som utökar EventEmitter som kan avge händelsen hello. Hur kan jag deklarera on-metoden med ett specifikt händelsenamn och listener-signatur?

class MyClass extends events.EventEmitter {

  emitHello(name: string): void {
    this.emit('hello', name);
  }

  // compile error on below line
  on(event: 'hello', listener: (name: string) => void): this;
}

Svar #1

Det mest användbara sättet att göra detta är att använda declare:

declare interface MyClass {
    on(event: 'hello', listener: (name: string) => void): this;
    on(event: string, listener: Function): this;
}

class MyClass extends events.EventEmitter {
    emitHello(name: string): void {
        this.emit('hello', name);
    }
}

Observera att om du exporterar din klass måste både gränssnittet och klassen deklareras med export-nyckelordet.

Svar #2

För att utöka @SergeyK:s svar, med detta kan du få typkontroll och komplettering på både emit och on-funktioner utan att upprepa händelsetyper.

  1. Definiera signaturer för händelselyssnare för varje händelsetyp:
interface MyClassEvents {
  'add': (el: string, wasNew: boolean) => void;
  'delete': (changedCount: number) => void;
}
  1. Deklarera ett gränssnitt som konstruerar typer för MyClass, baserat på EventListeners (MyClassEvents) funktionssignatur:
declare interface MyClass {
  on<U extends keyof MyClassEvents>(
    event: U, listener: MyClassEvents[U]
  ): this;

  emit<U extends keyof MyClassEvents>(
    event: U, ...args: Parameters<MyClassEvents[U]>
  ): boolean;
}
  1. Definiera helt enkelt din klass som utökar EventEmitter:
class MyClass extends EventEmitter {
  constructor() {
    super();
  }
}

Nu kommer du att få typkontroll för on och emit funktioner:

Tyvärr får du bara komplettering och typkontroll på de två funktionerna (om du inte definierar fler funktioner inom MyClass-gränssnittet).

För att få en mer generell lösning kan du använda det här paketet jag skrev. obs: det lägger till ingen körningstidsoverhead.

import { TypedEmitter } from 'tiny-typed-emitter';

interface MyClassEvents {
  'add': (el: string, wasNew: boolean) => void;
  'delete': (changedCount: number) => void;
}

class MyClass extends TypedEmitter<MyClassEvents> {
  constructor() {
    super();
  }
}

Svar #3

Här är vad jag kunde klura ut. Åsidosätt standardfunktionen med en generisk!

interface IEmissions {
  connect: () => void
  test: (property: string) => void
}

class MyClass extends events.EventEmitter {
  private _untypedOn = this.on
  private _untypedEmit = this.emit
  public on = <K extends keyof IEmissions>(event: K, listener: IEmissions[K]): this => this._untypedOn(event, listener)
  public emit = <K extends keyof IEmissions>(event: K, ...args: Parameters<IEmissions[K]>): boolean => this._untypedEmit(event, ...args)

  this.emit('test', 'Testing') // This will be typed for you!
}

// Example:
const inst = new MyClass()
inst.on('test', info => console.log(info)) // This will be typed!

Svar #4

Jag är inte säker på från när, men detta är tillgängligt i node-typningarna nu. Du behöver bara skicka en Record<string, any[]> (event-nyckel > argument-tuple) typ till generiken av EventEmitter<T>

import { EventEmitter } from 'events';

interface MyClassEvents {
  "hello": [name: string];
}

class MyClass extends EventEmitter<MyClassEvents> { }

const foo = new MyClass();

detta kommer att typa EventEmitter:s metoder så att dessa händelser blir typade för användaren

Relaterade artiklar