如何防止TypeScript中意外的类型兼容

4ngedf3f  于 2023-02-25  发布在  TypeScript
关注(0)|答案(3)|浏览(138)

假设我们有一个接口Animal

interface Animal {
  amountOfLegs: number;
}

然后两个不同的类DogSnake实现这个接口:

class Dog implements Animal {
  constructor(public amountOfLegs: number) {
  }
}

class Snake implements Animal {
  amountOfLegs: number;

  constructor() {
    this.amountOfLegs = 0;
  }
}

然后我们要创建一个特定于Snake的函数:

function functionSpecificForSnake(snake: Snake): any {
  //Do something with the snake object...
}

但是当函数被一个Dog对象调用时,TS并不抱怨错误的类型:

functionSpecificForSnake(new Dog(4)); // No compiler error, but possible runtime error?

Playground链接
所以我的问题是:我知道这叫做type compatibility,但是我没有找到一种方法来阻止它。在我的tsconfig.json中使用"strictFunctionTypes": true选项似乎没有任何作用。

ws51t4hk

ws51t4hk1#

你的类DogSnakestructurally等价类型,所以就Typescript而言,它们是可以相互赋值的。你想要的行为是nominal type system的行为,Typescript没有,但是你可以通过改变类型使之在结构上不同来模拟它。
为此,您可以添加一个名为__brand之类的属性,它不会与类型可能具有的任何真实的属性冲突,并且该属性对于每个类可以具有不同的类型;它可以简单地是类本身,或者是类的名称作为字符串文字。为了避免在运行时的任何开销,你可以声明属性而不初始化它,这样属性就不真正存在,但是Typescript认为它存在。要禁用未初始化属性的错误,你可以使属性可选,或者使用!而不是?来向编译器隐瞒正在初始化的属性。

class Dog implements Animal {
  private readonly __brand?: Dog;

  constructor(public amountOfLegs: number) {}
}

class Snake implements Animal {
  private readonly __brand?: Snake;

  amountOfLegs: number;
  constructor() {
    this.amountOfLegs = 0;
  }
}

然后,如果您尝试在预期Snake的地方使用Dog,则会得到类型错误,因为__brand属性的类型错误。
Playground链接

3okqufwl

3okqufwl2#

我相信TypeScript会检查属性类型,所以您的问题是DogSnake具有相同的属性和类型...所以它们是相同的。
请参见TypeScriptPlayground。

interface Animal {
  amountOfLegs: number;
}
class Dog implements Animal {
  constructor(public amountOfLegs: number) {
  }
}
class Snake implements Animal {
  public amountOfLegs: number;
  public venomous: boolean;
  constructor() {
    this.amountOfLegs = 0;
    this.venomous = true;
  }
}
function functionSpecificForSnake(snake: Snake): void {
  //Do something with the snake object...
}
functionSpecificForSnake(new Dog(4));
hyrbngr7

hyrbngr73#

就目前情况而言,不,你不能。在你引用的链接中:“为了检查y是否可以赋给x,编译器检查x的每个属性以在y中找到对应的兼容属性”。因此编译器忽略类的“implements interfaces”位,而只查看每个类的形状。
蛇和狗是相同的形状,因此可以指定给对方。
您可以尝试为每个类添加一个鉴别器,类似于:

interface Animal {
    disc: string
    amountOfLegs: number;
}

class Dog implements Animal {
    disc = 'Dog' as const
    constructor(public amountOfLegs: number) {
    }
}

class Snake implements Animal {
    disc = 'Snake' as const
    amountOfLegs: number;

    constructor() {
        this.amountOfLegs = 0;
    }
}

function functionSpecificForSnake(snake: Snake): any {
    //Do something with the snake object...
}

functionSpecificForSnake(new Dog(4))

这将给出错误
“Dog”类型的参数不能赋给“Snake”类型的参数。属性“disc”的类型不兼容。类型““Dog”"不能赋给类型““Snake”“。(2345)

相关问题