繁体   English   中英

通用联合类型执行交集类型的棘手输入问题

[英]Tricky typing problems with a generic union type doing an intersection type

我偶然发现了一个特殊的问题,我不知道如何解决这个问题。 我有一个通用类型的 class。 这个 class 包含一个只有一个联合参数的方法要求通用类型或一个 object。由于这个 class 用不同的类型多次实例化,我想创建一个通用方法来检索其中一个实例(通用类型),并使内部方法的调用。

这是复制代码:

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”属于 MyClasses[T] 类型,而 myData 采用与 MyClasses[T] 关联的泛型类型,因此应该可以正确推断出所有内容。

那么,为什么 typescript 试图做我的类型的交集?

你得到交集的原因是因为编译器忘记了key类型和myData类型之间的相关性,所以它只接受它知道是安全的东西。 如果myData既是AB (如{a: 1, b: 2} ),那么无论传入哪个key ,调用myClassInstance.myMethod(myData)都是安全的。

这是因为myClassInstance.myMethod被视为具有联合类型而发生的情况:

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

如果你调用一个函数的联合, 你必须传入它的参数的交集,编译器才能接受它。

当然,不可能有人传入不匹配的myDatakey (实际上这不是真的,因为key可能是联合类型。如果没有ms/TS#27808 ,这是可能的。假设这是可能的,但不太可能。或者至少我们不会在这里担心它。)但是编译器看不到这个。

编译器并不真正支持查看不同类型之间任意复杂的相关性。 基本问题在microsoft/TypeScript#30581中进行了描述,它根据相关联合来构建它。


幸运的是,通常有一种方法可以以编译能够遵循逻辑的方式重构类型。 这在microsoft/TypeScript#47109中有描述。 这个想法是制作一个尽可能简单地表示您的输入/输出关系的“基本”类型:

interface MyClassGenerics {
    a: A;
    b: B
}

然后我们将MyClasses明确地写为该基本类型的映射类型:

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的类型被视为单个 function 类型,其输入和 output 类型取决于通用类型参数K 它不再是联合体,因此可以更轻松地调用它。

游乐场代码链接

暂无
暂无

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

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