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的 on 签名助手
// 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;
}
}
当我们有一个泛型类型参数时,编译器将无法解析条件类型,它看到的是你试图将一个函数分配给一个奇怪的条件类型的字段,并且会决定,因为它可以' t 检查它是不安全的。
您可以创建一个您将分配给的中间受保护方法,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
在第一种情况下需要 override 那样覆盖onInternal
),并且派生类的客户端不会更聪明并且具有回调类型.
推荐阅读
- html - Perl 添加围绕 HTML/XML 标记中的单词
- pandas - 如何识别两个熊猫数据框中的共同元素
- android - FFmpeg - 混合视频和音频时大文件的 ANR
- python - Iconbitmap :: 在tkinter python3中找不到文件
- selenium - 找不到可点击元素的唯一 xpath
- c# - 在.Net中,将“图像”与另一张图像转换时,会影响其中一张图像的颜色质量
- sql - 如何使用两个/组合参数 1 日期和 1 时间与条件设置数据集参数
- azure - Terraform private azure load balancer issue
- java - 如何为多用户 Java 应用程序设计/保留用户角色(当前和已注册)?
- java - 如何针对列表中的某些数据单击复选框