【问题标题】:Static Factory Method Return Type of Generic Class泛型类的静态工厂方法返回类型
【发布时间】:2021-07-26 10:12:04
【问题描述】:

我正在尝试为父类是泛型的类族推断静态工厂方法的正确类型。我希望静态工厂方法的返回类型是父类以抽象两个子类,但 typescript 会推断出两个子类的一个或类型。

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

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

Playground

【问题讨论】:

  • 请考虑修改此问题中的代码以构成minimal reproducible example,将其放入像The TypeScript Playground (link to code) 这样的独立IDE 时,可以清楚地说明您面临的问题。这将使那些想要帮助您的人立即着手解决问题,而无需首先重新创建它。它将使您得到的任何答案都可以针对定义明确的用例进行测试。
  • 您可以将返回类型 注释 为您想要的任何类型,但我对示例代码的理解不足以知道该建议什么。你几乎肯定不想使用NumberString 类型,因为它们是很少使用的包装对象,而不是人们一直使用的numberstring 原语。 guard 应该是什么?它从何而来? ensureShouldBeA 是什么?它从何而来? event 是什么类型?你写了any,但它肯定不可能是任何东西,不是吗?你打算传递什么样的东西?
  • 我只是编辑帖子,我希望它得到更好的解释

标签: typescript oop types


【解决方案1】:

认为您遇到的问题是 TypeScript 编译器目前无法使用 control flow analysis 来缩小 泛型类型参数,如下面的 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 &amp; string。但这不会导致编译器说T 本身现在是string。类型保护缩小了的类型,而不是通用类型参数。由于T 没有缩小为stringParent&lt;T&gt; 类型也没有缩小为Parent&lt;string&gt;,因此ChildA 不被视为可分配给Parent&lt;string&gt;


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


除非有什么改变,否则我的建议是,当你比编译器更了解某个值的类型时,我的建议是做你总能做的事情:使用type assertion知道当typeof guard === "string"ChildA可以赋值给Parent&lt;T&gt;,所以只需告诉编译器:

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')
  }
}

这解决了错误。 (请注意,如果编译器没有看到Typetypeof value 充分相关,那么当您编写value as Type 时,您的实际代码仍可能产生错误。如果是这样,您仍然可以通过value as unknown as Type 或@987654347 进行断言@.)

请注意类型断言,因为现在您有责任在这些行中验证类型安全性。编译器无法做到这一点,如果您的断言有误,编译器也无法注意到。不小心对编译器撒谎可能会导致运行时出现奇怪的行为。所以在断言之前仔细检查new ChildA(guard)new ChildB(guard) 是否真的是Parent&lt;T&gt; 类型。

Playground link to code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    • 1970-01-01
    • 1970-01-01
    • 2012-11-12
    相关资源
    最近更新 更多