TypeScript在静态实现中推断出更通用的类型

时间:2018-03-01 18:59:48

标签: typescript oop generics constructor

我偶然发现了TypeScript中的奇怪情况,其中包含以下代码:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait as wait
from selenium.webdriver.support import expected_conditions as EC

wait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, '//td[contains(., "Fiscal Yr")]/following-sibling::td/a[img[@title="Edit Filter"]]'))).click()

正如您所看到的,function staticImplements<T>() { return (constructor: T) => {}; } enum FruitList { APPLE, BANANA, PEAR, } interface FruitInterface { fruit: FruitList; } abstract class Fruit implements FruitInterface { fruit: FruitList; constructor(fruit: FruitList) { this.fruit = fruit; } } interface AppleConstructor { new(fruit: FruitList.APPLE): AppleInterface; } interface AppleInterface extends Fruit { fruit: FruitList.APPLE; } class Apple extends Fruit implements AppleInterface { fruit: FruitList.APPLE; constructor(fruit: FruitList) { super(fruit); } } staticImplements<AppleConstructor>()(Apple); 的构造函数需要Fruit类型的参数fruit,子类FruitList的构造函数也是如此,但字段{{} 1 {}} Apple只需要枚举fruit的值AppleInterface,而不是enum所拥有的所有可能值,就像它的父APPLE一样。 FruitList期望参数FruitInterface的类型为AppleConstructor,用于检查fruit static是否实现了函数{{1}的接口在最后一行。问题是,TypeScript声明它确实没有,但这怎么可能?

1 个答案:

答案 0 :(得分:2)

您的基本问题是TypeScript类型系统有些不健全(因此您可以编写一些非类型安全的代码)。健全性是TypeScript的not a goal,但如果这个错误很常见,如果你打开issue in GitHub,他们可能会对它进行解决。我找不到你的确切问题。

这里特别不健康与不执行type variance有关。简而言之,属性读取的子类型可以是协变(子类可以缩小其只读属性),但属性写入只能是 contravariant (子类应加宽其只写属性)。如果要同时读取和写入属性,则必须不变才能成为声音。

TypeScript允许子类属性为协变。这意味着当你阅读属性时,事情通常会很好地工作,但是当你写它们时有时会发生不好的事情。

让我用更少的代码重申主要问题:

interface A {
  x: string | number
}
interface B extends A {
  x: number
}
const b: B = {x: 0};
const a: A = b;
a.x = "whoops"; // no error
b.x; // number at compile time, but string at runtime
b.x.toFixed(); // works at compile time, error at runtime

了解B如何被视为A的子类型,在您尝试将错误的内容写入其属性之前,这是正常的。人们倾向于不这样做,所以语言维护者不管它,因为防止这个问题是困难的并且真的有限(你真的想要只写属性吗?)。

在您的情况下,您的子类正在调用超类的构造函数方法来编写(更宽)属性,即使该属性已被子类缩小。这是同一个问题。

因此,这是解决特定问题的可能方法:使用泛型来指定实际约束,以便缩小/扩展仅在您期望的位置发生:

interface FruitInterface<T extends FruitList> {
  fruit: T;
}

abstract class Fruit<T extends FruitList = FruitList> implements FruitInterface<T> {
  fruit: T;

  constructor(fruit: T) {
      this.fruit = fruit;
  }
}

interface AppleConstructor {
  new(fruit: FruitList.APPLE): AppleInterface;
}

interface AppleInterface extends Fruit<FruitList.APPLE> {
}

class Apple extends Fruit<FruitList.APPLE> implements AppleInterface {
  constructor(fruit: FruitList) {
      super(fruit); // now error as you expect
  }
}

要解决上述错误,您应该将构造函数更改为仅FruitList.APPLE

希望有所帮助;祝你好运!