Typescript:键入定义为泛型函数类型交集的接口方法的实现,其中参数共享基本类型



TL;博士

只要看看代码块,你可能会发现问题所在。

链接到打字稿游乐场

示例用例

目的是指定一个事件发射器接口,该接口是事件发射器类可以实现的,并且可用于安全地键入对发射器的方法调用。由于事件管理对于不同的事件发射器类基本相同,因此应实现一个基本发射器类,该类可由其他发射器类扩展。

为了最小化冗余并最大程度地提高便利性,事件签名在由发射器类定义的一个接口中简化为事件名称和侦听器签名,并传递给发射器接口。

以下示例说明了上述内容。为简单起见,事件发射器的唯一功能是通过名为on的方法绑定新侦听器。

interface MyEvents
{
foo(x: number): void;
bar(): void;
moo(a: string, b: Date): void;
}
export class MyEventEmitter implements EventEmitter<MyEvents>
{
public on(event: 'foo', listener: (x: number) => void): this;
public on(event: 'bar', listener: () => void): this;
public on(event: 'moo', listener: (a: string, b: Date) => void): this;
public on(_event: string, _listener: any): this
{ return this; }
}

设置

  • 从 https://stackoverflow.com/a/50375286/2477364 并集到交叉转换器
  • 来自
  • https://stackoverflow.com/a/50375712/2477364/https://stackoverflow.com/a/52368058/2477364 签名的助手
// union to intersection converter from https://stackoverflow.com/a/50375286/2477364
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
// helpers for the on signature from https://stackoverflow.com/a/50375712/2477364
type OnSignatures<ListenersT, ReturnT> =
{ [EventT in keyof ListenersT]: (event: EventT, listener: ListenersT[EventT]) => ReturnT };
type OnAll<ListenersT, ReturnT> =
UnionToIntersection<OnSignatures<ListenersT, ReturnT>[keyof ListenersT]>;
// the actual event emitter interface
export interface EventEmitter<ListenersT>
{
on: OnAll<ListenersT, this>;
}

这变得非常具体,但我想过缩小示例,但我无法找出一个最小的例子。与其中和所有内容,我认为使用用例会更容易遵循。

问题

现在进入实际问题。实现任何事件发射器的基本功能的类,可由其他事件发射器扩展或混合。

export abstract class BaseEventEmitter<ListenersT> implements EventEmitter<ListenersT>
{
public on<EventT extends keyof ListenersT>(_event: EventT, _listener: ListenersT[EventT]): this
{ return this; }
}

打字稿不喜欢我在这里on的方法:

Property 'on' in type 'BaseEventEmitter<ListenersT>' is not assignable to the same property in base type 'EventEmitter<ListenersT>'. Type '<EventT extends keyof ListenersT>(_event: EventT, _listener: ListenersT[EventT]) => this' is not assignable to type 'UnionToIntersection<OnSignatures<ListenersT, this>[keyof ListenersT]>'.

不幸的是,创建一个以简单方式实现接口的泛型类是不可能的。

如果类不是泛型的,编译器将能够重新创建on的签名并执行所需的检查。例如,这有效:

export class MyEventEmitter implements EventEmitter<MyEvents>
{
public on(event: keyof MyEvents, listener: MyEvents[keyof MyEvents]): this {
return this;        
}
}

当我们有一个泛型类型参数时,编译器将无法解析条件类型,它看到的是你正在尝试将函数分配给某个奇怪的条件类型的字段,并且会决定因为它无法检查它是不安全的。

您可以创建一个将分配给on的中间受保护方法,但您需要一个类型断言:

export abstract class BaseEventEmitter<ListenersT> implements EventEmitter<ListenersT>
{
protected onInternal(event: keyof ListenersT, listener: ListenersT[keyof ListenersT]): this {
return this;        
}
public on: EventEmitter<ListenersT>['on'] = this.onInternal as any;
}
export class MyEventEmitter extends BaseEventEmitter<MyEvents>
{
}

或者,您可以声明您的类,并将其类型化为返回EventEmitter的类的构造函数的常量

export abstract class BaseEventEmitterImpl<ListenersT>
{
public on(event: keyof ListenersT, listener: ListenersT[keyof ListenersT]): this {
return this;        
}
}
const BaseEventEmitter: new <T>() => EventEmitter<T> & BaseEventEmitterImpl<T> = BaseEventEmitterImpl as any;
export class MyEventEmitter extends BaseEventEmitter<MyEvents>
{
}
new MyEventEmitter().on('foo', x => 1);

这两种解决方案都不理想,但创建派生类将主要按预期工作(除非您想覆盖on因为在第一种情况下您需要重写onInternal(,并且派生类的客户端不会更明智并且具有回调类型。

相关内容

最新更新