简体   繁体   English

Infer 在 generics 作为第二个参数中不起作用

[英]Infer does not work in generics as second param

does anyone know why in the second case the infer does not display the desired type?有谁知道为什么在第二种情况下推断不显示所需的类型?

playground 操场

type Emmit<C extends Controller<any, any>> = C extends Controller<infer T, any> ? T : never
type On<C extends Controller<any, any>> = C extends Controller<infer E, infer O> ? O : never

type E = BaseEvent<"a", 1> | BaseEvent<"b", 2>
type O = BaseEvent<"c", 3> | BaseEvent<"d", 4>

class A extends Controller<E, O> {
    
}

type a = Emmit<A> // BaseEvent<"a", 1> | BaseEvent<"b", 2>;
type b = On<A>; // BaseEvent<string, any>

Cause原因

Extending this answer, the type inference for generics in class is done扩展答案,class 中 generics 的类型推断已完成

  • based on properties of the class基于 class 的属性
  • based on type inference from methods.基于方法的类型推断。

The type of the first generic is inferred correctly inferred from the parameter of the method emit which will be BaseEvent<"a", 1> | BaseEvent<"b", 2>第一个泛型的类型是从方法emit的参数中正确推断出来的,该参数将是BaseEvent<"a", 1> | BaseEvent<"b", 2> BaseEvent<"a", 1> | BaseEvent<"b", 2> in case of class A . BaseEvent<"a", 1> | BaseEvent<"b", 2>在 class A的情况下。

But for the second generic, OnEvent is used in on method only, which is again a generic and will not be inferred until on is called.但是对于第二个泛型, OnEvent仅在on方法中使用,这又是一个泛型,在调用on之前不会被推断。 So the TS is not able to infer the correct type.因此 TS 无法推断出正确的类型。 It is only inferring the constraining type ie BaseEvent or BaseEvent<string, any> .它只是推断约束类型,即BaseEventBaseEvent<string, any>

Even if you change on method to-即使您将方法更改on -

on(
  event: EventType<OnEvent>,
  listener: OnListener<EventPayloadByType<EventType<OnEvent>, OnEvent>>
): void {
  this.emitter.on(event, listener);
}

it will not infer correctly, as the type information OnEvent is not stored as it is but with computed types using EventType and OnListener which is, I think, out of the capability of TS as of now.它不会正确推断,因为类型信息OnEvent不是按原样存储的,而是使用EventTypeOnListener的计算类型存储的,我认为,到目前为止,这超出了 TS 的能力。

Possible solution可能的解决方案

Best solution that I can think of is adding a dummy property like private _dummy:: OnEvent我能想到的最佳解决方案是添加一个虚拟属性,如private _dummy:: OnEvent

declare class EventEmitter {
    emit(t: string, p: any): void
    on(e: string, f: Function): void
}

export interface BaseEvent<Type extends string = string, Payload = any> {
    typ: Type,
    payload: Payload
}

export type EventType<Event extends BaseEvent> = Event extends BaseEvent<infer Type>
    ? Type
    : string

export type EventPayloadByType<
    Type extends string,
    Event extends BaseEvent
> =
    Event extends BaseEvent<Type, infer Payload> 
        ? Payload 
        : never

export type OnListener<Payload = any> = (payload: Payload) => void;

export class Emitter<EmmitEvent extends BaseEvent, OnEvent extends BaseEvent> {
    private readonly emitter = new EventEmitter();
    private _dummy!: OnEvent
    //          ^^^^^^ dummy property added here which stores type info for `OnEvent`
    
    emit(event: EmmitEvent): void {
        this.emitter.emit(event.typ, event.payload);
    }
    
    on<Event extends EmmitEvent | OnEvent, T extends EventType<Event>>(
        event: T, 
        listener: OnListener<EventPayloadByType<T, Event>>
    ): void {
        this.emitter.on(event, listener);
    }
}


export abstract class Controller<
    EmmitEvent extends BaseEvent,
    OnEvent extends BaseEvent
> extends Emitter<EmmitEvent, OnEvent> {

}


type Emmit<C extends Controller<any, any>> = C extends Controller<infer T, any> ? T : never
type On<C extends Controller<any, any>> = C extends Controller<any, infer O> ? O : never

type E = BaseEvent<"a", 1> | BaseEvent<"b", 2>
type O = BaseEvent<"c", 3> | BaseEvent<"d", 4>

class A extends Controller<E, O> {
    
}

type a = Emmit<A> // BaseEvent<"a", 1> | BaseEvent<"b", 2>;
type b = On<A>; // BaseEvent<"c", 3> | BaseEvent<"d", 4>
// ^^^^ correctly inferred now

declare const e: A

// type of the argument in emit is `E` ie. BaseEvent<"a", 1> | BaseEvent<"b", 2>
e.emit({typ: "a", payload: 1})

Playground 操场

NOTE - I have changed some names in the original code注意- 我在原始代码中更改了一些名称

  • BaseEvent.type to BaseEvent.typ ( type is a keyword in TS, can cause bugs/errors) BaseEvent.typeBaseEvent.typtype是 TS 中的关键字,可能会导致错误/错误)
  • EmmitEvents to EmmitEvent (it is a type for a single event) EmmitEventsEmmitEvent (它是单个事件的类型)
  • OnEvents to OnEvent OnEventsOnEvent

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

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