簡體   English   中英

Typescript中可擴展的強類型事件發射器接口

[英]Extensible, strongly typed Event Emitter Interface in Typescript

我在Typescript中使用強類型事件發射器接口已有一段時間了,但是現在我需要它來支持向其添加自己的事件的子類。 在某些時候,Typescript無法識別基類事件。

以下是壓縮版本中的代碼( Playground Link ):

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

type AddParameters<ListenersT, EventT> =
    ListenersT extends (...args: infer ArgsT) => void
        ? (event: EventT, ...args: ArgsT) => Promise<boolean>
        : never;

type EmitSignatures<ListenersT> =
    { [EventT in keyof ListenersT]: AddParameters<ListenersT[EventT], EventT> };
type EmitAll<ListenersT> = UnionToIntersection<EmitSignatures<ListenersT>[keyof ListenersT]>

type OnSignatures<ListenersT, ReturnT> =
    { [EventT in keyof ListenersT]: (event: EventT, listener: ListenersT[EventT]) => ReturnT };
type OnAll<ListenersT, ReturnT> =
    UnionToIntersection<OnSignatures<ListenersT, ReturnT>[keyof ListenersT]>;

type EventEmitter<ListenersT> = EmitterInterface<ListenersT>;

export interface EmitterInterface<ListenersT>
{
    emit: EmitAll<ListenersT>;
    on: OnAll<ListenersT, this>;
}

/////////////////////////////////////////////////////////////////////////////////////////////

interface VehicleEvents
{
    accelerate(acceleration: number): void;
    brake(deceleration: number): void;
}

interface BusEvents extends VehicleEvents
{
    doorStateChange(front: boolean, middle: boolean, rear: boolean): void
}

interface Vehicle<E extends VehicleEvents> extends EventEmitter<E>
{
    onSig: OnSignatures<E, this>;
    onSigs: OnSignatures<E, this>[keyof E];
}

class Vehicle<E extends VehicleEvents>
{
    public constructor()
    { this.on('brake', () => this.flashBrakeLights()); } // supposed to work?

    public flashBrakeLights(): void {}

    public hitTheGas(strength: number): void
    { this.emit('accelerate', strength * 42); } // supposed to work?

    public test(): void
    {
        this.onSig.accelerate;
        this.onSig.brake;
        this.onSigs('accelerate', (a) => undefined); // error I don't understand
        this.onSigs('brake', (d) => undefined); // error I don't understand
        this.onSigs('foo', () => undefined); // supposed to error
    }
}

interface Bus extends EventEmitter<BusEvents> {}

class Bus extends Vehicle<BusEvents>
{
    public doorState: [boolean, boolean, boolean] = [false, false, false];

    public constructor()
    {
        super();
        this.on('accelerate', () => {
            this.door(0, false);
            this.door(1, false);
            this.door(2, false);
        });
    }

    public door(index: number, state: boolean): void
    {
        this.doorState[index] = state;
        this.emit('doorStateChange', ...this.doorState);
    }
}

export const bus = new Bus();

E類型被聲明為VehicleEvents擴展,應該足以讓Typescript知道存在acceleratebrake事件,不是嗎?

為什么這不起作用的任何解釋? 關於如何解決此問題或以其他方式實現我所需要的任何想法?

問題在於,如果類中仍然包含未解析的類型參數,則無法解析那些花哨的條件類型(不確定它們來自cough的來源 )。 因此,盡管使用通用類型參數進行可擴展性的方法似乎是一個好主意,但其效果是,它使類內部的對象無法emit on並且在類內部emit不可用的信息。

一種解決方案是不使用類型參數,而僅使用事件接口本身。 的問題(無疑是您發現的)是,它使類無法擴展,因為onemit任何派生版本都不與基本類型版本兼容。

為了解決這個問題,我們可以使用一個從基本類型on移除並emit的函數。 這有點駭人聽聞,但我認為沒有更好的方法。

interface VehicleEvents {
  accelerate(acceleration: number): void;
  brake(deceleration: number): void;
}

interface BusEvents extends VehicleEvents {
  doorStateChange(front: boolean, middle: boolean, rear: boolean): void
}

interface Vehicle extends EventEmitter<VehicleEvents> {}

class Vehicle {
  public constructor() {
    this.on('brake', () => this.flashBrakeLights()); //ok 
  }

  public flashBrakeLights(): void { }

  public hitTheGas(strength: number): void { this.emit('accelerate', strength * 42); } // ok

}

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
interface Bus extends EventEmitter<BusEvents> { }

function extendEmitter<TBaseCtor extends new (...a: any[])=> any>(ctor: TBaseCtor){
  return ctor as (new (...a: ConstructorParameters<TBaseCtor>) => Omit<InstanceType<TBaseCtor>, 'on' | 'emit'>)
}
class Bus extends extendEmitter(Vehicle) {
  public doorState: [boolean, boolean, boolean] = [false, false, false];

  public constructor() {
    super();
    this.on('accelerate', () => {
      this.door(0, false);
      this.door(1, false);
      this.door(2, false);
    });
  }

  public door(index: number, state: boolean): void {
    this.doorState[index] = state;
    this.emit('doorStateChange', ...this.doorState);
  }

}

export const bus = new Bus();

上面的版本不能確保新派生的對象正確地實現了基本事件。 我們可以編寫一個版本來驗證這一點,但需要對原始定義進行一些改動,以允許我們從基本類型中提取事件接口:

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

type AddParameters<ListenersT, EventT> =
    ListenersT extends (...args: infer ArgsT) => void
        ? (event: EventT, ...args: ArgsT) => Promise<boolean>
        : never;

type EmitSignatures<ListenersT> =
    { [EventT in keyof ListenersT]: AddParameters<ListenersT[EventT], EventT> };
type EmitAll<ListenersT> = UnionToIntersection<EmitSignatures<ListenersT>[keyof ListenersT]>

type OnSignatures<ListenersT, ReturnT> =
    { [EventT in keyof ListenersT]: (event: EventT, listener: ListenersT[EventT]) => ReturnT };
type OnAll<ListenersT, ReturnT> =
    UnionToIntersection<OnSignatures<ListenersT, ReturnT>[keyof ListenersT]>;

type EventEmitter<ListenersT> = EmitterInterface<ListenersT>;

export interface EmitterInterface<ListenersT>
{
    emit: EmitAll<ListenersT>;
    on: OnAll<ListenersT, this> & {__source: ListenersT}; // do not use __source, just here to allow EventTypes to work
}

type EventTypes<T> = T extends EventEmitter<infer U> ? U : never;

/////////////////////////////////////////////////////////////////////////////////////////////

interface VehicleEvents {
  accelerate(acceleration: number): void;
  brake(deceleration: number): void;
}

interface BusEvents extends VehicleEvents {
  doorStateChange(front: boolean, middle: boolean, rear: boolean): void
}

interface Vehicle extends EventEmitter<VehicleEvents> {}

class Vehicle {
  public constructor() {
    this.on('brake', () => this.flashBrakeLights()); //ok 
  }

  public flashBrakeLights(): void { }

  public hitTheGas(strength: number): void { this.emit('accelerate', strength * 42); } // ok

}

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
interface Bus extends EventEmitter<BusEvents> { }


function extendEmitter<TBaseCtor extends new (...a: any[])=> any>(ctor: TBaseCtor){
  return function<TEvents extends EventTypes<InstanceType<TBaseCtor>>>(){
    return ctor as (new (...a: ConstructorParameters<TBaseCtor>) => Omit<InstanceType<TBaseCtor>, 'on' | 'emit'> & EventEmitter<TEvents>)
  }
}

class Bus extends extendEmitter(Vehicle)<BusEvents>() {
  public doorState: [boolean, boolean, boolean] = [false, false, false];

  public constructor() {
    super();
    this.on('accelerate', () => {
      this.door(0, false);
      this.door(1, false);
      this.door(2, false);
    });
  }

  public door(index: number, state: boolean): void {
    this.doorState[index] = state;
    this.emit('doorStateChange', ...this.doorState);
  }

}

export const bus = new Bus();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM