我偶然发现了一个特殊的问题,我不知道如何解决这个问题.我有一个具有泛型类型的类.此类包含一个方法,该方法只有一个联合参数请求泛型类型或对象.因为这个类使用不同的类型被多次实例化,所以我想创建一个泛型方法来检索其中一个实例(泛型类型),并在其中调用该方法.

这里有一个复制代码:

export interface A {
  a: any;
}
export interface B {
  b: any;
}
export interface MyClasses {
  a: MyClass<A>;
  b: MyClass<B>;
}

export interface C {
  c: any;
}
export declare class MyClass<T = { [prop: string]: any }> {
  myMethod(data: T | C): T;
}

export type MyClassesKeys = keyof MyClasses;
export type MyClassInferGenericType<T> = T extends MyClass<infer G> ? G : T;

export class MyService {
  private myClasses: MyClasses = {
    a: new MyClass<A>(),
    b: new MyClass<B>()
  };

  public getMyClass<T extends MyClassesKeys>(keyName: T): MyClasses[T] {
    return this.myClasses[keyName];
  }

  public callerMethod<T extends MyClassesKeys, U extends MyClassInferGenericType<MyClasses[T]>>(key: T, myData: U) {
    // This part is usually retrieved gener
    const myClassInstance = this.getMyClass(key);

    // My call
    myClassInstance.myMethod(myData);
  }
}

这会以某种方式返回一个编译错误:

Argument of type 'A | B' is not assignable to parameter of type '(A | C) & (B | C)'.
  Type 'A' is not assignable to type '(A | C) & (B | C)'.
    Type 'A' is not assignable to type 'A & C'.
      Property 'c' is missing in type 'A' but required in type 'C'.

由于"myClassInstance"属于MyClasss[T]类型,并且mydata采用与MyClasss[T]相关联的泛型类型,所以一切都应该被正确地推断出来.

So, why is typescript trying to do an intersection of my types?

推荐答案

得到交集的原因是因为编译器忘记了key类型和myData类型之间的相关性,所以它只接受knows是安全的东西.如果myDataboth A and B(如{a: 1, b: 2}),则无论传入哪个key,都可以安全地呼叫myClassInstance.myMethod(myData).

这就是为什么会发生这种情况,因为myClassInstance.myMethod被认为有union type分:

const x = myClassInstance.myMethod;
// const x: ((data: A | C) => A) | ((data: B | C) => B)

如果调用函数的并集,则编译器会接受you have to pass in an intersection of its parameters.

当然,没有正确匹配的myDatakey是不可能通过的.(实际上并非如此,因为key可能是联合类型.减go ms/TS#27808,等于possible.让我们假设这是可能的,但不太可能.或者至少我们在这里不会担心它.)但是编译器看不到这一点.

实际上并不支持编译器查看不同类型之间的任意复杂关联.基本问题在microsoft/TypeScript#30581中描述,它以correlated unions为框架.


幸运的是,通常有一种方法可以以编译器is能够遵循逻辑的方式重构类型.这是在第microsoft/TypeScript#47109节中描述的.我们的 idea 是创建一个尽可能简单地表示您的输入/输出关系的"基本"类型:

interface MyClassGenerics {
    a: A;
    b: B
}

然后我们将MyClasses显式地写为mapped type,覆盖该基本类型:

type MyClasses = {
    [K in keyof MyClassGenerics]: MyClass<MyClassGenerics[K]>
}

现在,当您编写MyClasses[K]时,编译器将自动看到它与MyClass<MyClassGenerics[K]>的关系.你可以引用MyClassGenerics[K],而不需要使用MyClassInferGenericType:

public callerMethod<K extends MyClassesKeys>(key: K, myData: MyClassGenerics[K]) {
  const myClassInstance = this.getMyClass(key);

  const x = myClassInstance.myMethod
  // const x: (data: C | MyClassGenerics[K]) => MyClassGenerics[K]

  myClassInstance.myMethod(myData); // okay
}

现在可以看到,myClassInstance.myMethod的类型是单个函数类型,其输入和输出类型取决于泛型类型参数K.它不再是一个unions ,因此可以更容易地被称为unions .

Playground link to code

Typescript相关问答推荐

TS不推断类型在条件判断后具有属性'

如何按不能保证的功能过滤按键?

PrimeNG日历需要找到覆盖默认Enter键行为的方法

接口中函数的条件参数

带下拉菜单的Angular 响应式选项卡

在Typescript 中,有没有`index=unfined的速记?未定义:某个数组[索引]`?

对于始终仅在不是对象属性时才抛出的函数,返回Never

TypeScrip:从对象中提取和处理特定类型的键

我怎样才能正确地输入这个函数,使它在没有提供正确的参数时出错

抽象类对派生类强制不同的构造函数签名

Angular NG8001错误.组件只能在一个页面上工作,而不是在她的页面上工作

KeyOf关键字在特定示例中是如何工作的

如何为带有参数的函数类型指定类型保护?

作为参数的KeyOf变量的类型

如何知道使用TypeScript时是否在临时工作流内运行

Svelte+EsBuild+Deno-未捕获类型错误:无法读取未定义的属性(读取';$$';)

从泛型对象数组构造映射类型

在ts中获取级联子K类型?

Typescript 确保通用对象不包含属性

从接口推断模板参数?