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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TypeScript在静态实现中推断出更通用的类型相关的知识,希望对你有一定的参考价值。

我偶然发现了TypeScript中的奇怪情况,代码如下:

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,子类Apple的构造函数也是如此,但fruit的字段AppleInterface只需要枚举APPLE的值FruitList而不是enum所持有的所有可能值它的父母FruitInterfaceAppleConstructor也是如此,它期望参数fruitFruitList.APPLE类型,用于检查Apple static是否在最后一行实现了与staticImplements函数的接口。问题是,TypeScript声明它确实没有,但这怎么可能呢?

答案

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

这里特别不健康与不执行type variance有关。简而言之,子类型的属性读取可以是协变的(子类可以缩小它们的只读属性),但属性写入只能是逆变的(子类应该扩展它们的只写属性)。如果属性既可以读写,又必须是不变的。

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

希望有所帮助;祝好运!

以上是关于TypeScript在静态实现中推断出更通用的类型的主要内容,如果未能解决你的问题,请参考以下文章

Typescript 推断的泛型类型在条件类型中是未知的

来自接口实现的 Typescript 泛型推断

LayaBox---TypeScript---类型推论

使用联合类型进行类型推断 - 不存在最佳通用类型

在 TypeScript 中推断泛型函数类型

TypeScript——类型检查机制