详解 TypyScript 的一个怪异行为
以下为译文:
最近我在阅读 Asana 的博客时,看到了一篇关于 TypeScript 怪异行为的文章(https://blog.asana.com/2020/01/typescript-quirks/),文中提到的第一条 TypeScript 的怪异行为引起了我的兴趣。尽管看上去似乎前后不一致,但实际上这种类型系统的行为完全合乎逻辑。
该文章使用了接口 Dog 和函数 printDog 作为例子:
interface Dog {
breed: string
}
function printDog(dog: Dog) {
console.log("Dog: " + dog.breed)
}
这段代码模拟了 TypeScript 中的一种流行写法。似乎到目前为止没什么问题,但这篇文章介绍了一个看似“前后不一致”的情况。尽管你可以把一个包含 breed 属性的对象先赋值给一个变量再传递给该函数,但你不能直接将这个对象传递给该函数。
const ginger = {
breed: "Airedale",
age: 3
};
printDog(ginger); //works
printDog({
breed: "Airedale",
age: 3
}); //fails
这是为什么?TypeScript 会检查对象中的额外属性,并发现bug,但为什么只有在直接将对象传递给函数时才会出问题?
这篇文章指出,TypeScirpt 是一个“结构化类型语言”,也就是说类型检查是根据对象的结构进行的,而不是根据它的继承关系进行的。但这条规则有一个例外,那就是当开发明确指定变量类型的时候。而且 TypeScript 函数的参数是协变的,这意味着它可以接受任何子类型。在结构化类型语言中,给已有类型添加一个属性就相当于扩展该类型。
那么,这个错误是为什么呢?如果它允许传递任何指定类型的子类型,那么为什么有时候会出错?
要回答这个问题,需要回到我之前提到的类型系统上。在创建一个变量并传递给函数时,变量会推断一个类型。在创建函数中的对象时,我们明确告诉 TypeScript 变量是 Dog 类型。创建变量时会检查类型是否包含多余属性,但函数调用时不会,因为函数调用是协变的。这个“不一致”与函数调用也没关系,只要在创建对象时指定类型就会产生同样的行为:
const ginger: Dog = {
breed: "Airedale",
age: 3
};
这段代码会产生与函数类型相同的错误,因为age并不是Dog类型的属性。
虽然类似的问题经常会困扰 TypeScript 开发者,但类型系统的任何行为通常都有合理的解释。如果没有,那你就应该向微软报告错误。
原文:https://matthewmiller.dev/blog/demystifying-typescript-quirk/
本文为 CSDN 翻译,转载请注明来源出处。