代码之家  ›  专栏  ›  技术社区  ›  Neonit Humam Helfawi

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

  •  0
  • Neonit Humam Helfawi  · 技术社区  · 6 年前

    TL;博士

    Link to Typescript Playground

    示例用例

    其目的是指定一个事件发射器接口,可以实现哪些事件发射器类,也可以使用哪些事件发射器类安全地对发射器的方法调用进行类型化。由于事件管理对于不同的事件发射器类基本上是相同的,所以应该实现一个可以由其他发射器类扩展的基本发射器类。

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

    下面的示例说明了上述内容。为了简单起见,事件发射器的唯一功能是通过名为 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不喜欢我的 方法如下:

    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]>'.

    1 回复  |  直到 6 年前
        1
  •  0
  •   Titian Cernicova-Dragomir    6 年前

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

    on 并执行所需的检查。例如,这是有效的:

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

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

    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);
    

    这两种解决方案都不理想,但是创建派生类的工作基本上与预期的一样(除非您希望重写 在第一种情况下,您需要覆盖 onInternal ),派生类的客户端将不会更聪明,并且具有回调的类型。