泛型类的静态工厂方法返回类型

时间:2021-05-04 03:30:05

标签: typescript oop types

我正在尝试为一系列类推断静态工厂方法的正确类型,其中父类是通用的。我希望静态工厂方法的返回类型成为抽象两个子类的父类,但打字稿推断两个子类的 or 类型。

abstract class Parent<T> {
  abstract readonly property: T
}

class ChildA implements Parent<string> {
  constructor(readonly property: string) {}
}

class ChildB implements Parent<number> {
  constructor(readonly property: number) {}
}

class Factory {
  public static create(guard: any) /** I want to the return type be only Parent without indicate in a explicit way the generic **/ {
    if (typeof guard === 'string') {
      return new ChildA(guard)
    }
    if (typeof guard === 'number') {
      return new ChildB(guard)
    }

    return new UnkwonClass()
  }
}

我不知道如何将工厂签名描述为只返回 Parent 来抽象两个子类,因为两者都具有相同的形状,并且没有或类型 ChildA | ChildB

我尝试将签名写为父级,然后打字稿告诉我,父级是通用的,然后我将创建方法的签名更改为 public static create<T>(guard: any): Parent<T> 但我必须从实例传递类型,我希望 ts 推断出我传递给子类的类型。

Playground

1 个答案:

答案 0 :(得分:0)

认为您遇到的问题是 TypeScript 编译器目前无法使用 control flow analysis 来缩小泛型类型参数的范围,例如 {{1}在以下代码中:

T

当您检查 class Factory { public static create<T>(guard: T): Parent<T> { if (typeof guard === 'string') { return new ChildA(guard); // error! // Type 'ChildA' is not assignable to type 'Parent<T>' } if (typeof guard === 'number') { return new ChildB(guard); // error! // Type 'ChildB' is not assignable to type 'Parent<T>' } throw new Error('Unknown class') } } 时,编译器可以将 typeof guard === "string" 的明显类型从 guard 缩小到 T。但这不会导致编译器说T & string 本身现在是T。类型保护缩小了的类型,而不是泛型类型参数。并且由于 string 未缩小为 T,因此类型 string 未缩小为 Parent<T>,因此 Parent<string> 不被视为可分配给 {{1 }}。


GitHub 中有各种未解决的问题,需要在这里进行一些改进。一个好的开始是 microsoft/TypeScript#33014,它要求编译器通过控制流分析缩小类型参数,至少允许某些属性查找。从 TypeScript 4.2 开始,这个和相关的建议都没有实现......而且不清楚这里什么时候或者是否会发生任何变化。


直到并且除非发生某些变化,我的建议是当您比编译器更了解某个值的类型时,您总是可以做的事情:使用 type assertion知道 ChildA 可以在 Parent<string> 时赋值给 ChildA,所以只需告诉编译器:

Parent<T>

这解决了错误。 (请注意,如果编译器没有将 typeof guard === "string" 视为与 class Factory { public static create<T>(guard: T): Parent<T> { if (typeof guard === 'string') { return new ChildA(guard) as Parent<typeof guard>; // okay } if (typeof guard === 'number') { return new ChildB(guard) as Parent<typeof guard>; // okay } throw new Error('Unknown class') } } 充分相关,则在您编写 value as Type 时,您的实际代码可能仍会产生错误。如果是这样,您仍然可以通过Typetypeof value。)

请注意类型断言,因为现在您有责任在这些行中验证类型安全。编译器做不到,如果你的断言有误,编译器也不会注意到。不小心向编译器撒谎可能会导致运行时出现奇怪的行为。因此,在断言之前仔细检查 value as unknown as Typevalue as any as Type 是否真的属于 new ChildA(guard) 类型。

Playground link to code