一段时间以来,我一直在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知道存在accelerate
和brake
事件,不是吗?
任何解释为何无效?关于如何解决此问题或以其他方式实现我所需要的任何想法?
答案 0 :(得分:2)
问题是,在类内部,那些花哨的条件类型(不确定它们来自cough的位置)如果仍包含未解析的类型参数,则无法解析。因此,尽管使用通用类型参数进行可扩展性的方法似乎是一个好主意,但其效果是,它使on
和emit
在类中不可用。
一种解决方案是不使用类型参数,而仅使用事件接口本身。这样做的问题(无疑是您发现的)是,它使类无法扩展,因为on
和emit
的任何派生版本将与基本类型版本不兼容。
要解决此问题,我们可以使用从基本类型中删除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();