繁体   English   中英

打字稿:键入定义为通用函数类型交集的接口方法的实现,其中参数共享基本类型

[英]Typescript: Typing an Implementation of an Interface Method defined as a generic Function Type Intersection, where Arguments share a base Type

TL; DR

只要看一下代码块,您就可能会发现问题所在。

链接到打字稿游乐场

示例用例

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

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

以下示例说明了上述内容。 为简单起见,事件发射器的唯一功能是通过名为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; }
}

设定

// 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; }
}

Typescript不喜欢我的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]>'.

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

如果类是不通用的,编译器将能够rezolve的签名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 )和派生类的客户端将不会是明智的,并具有类型回调。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM