我很难理解TypeScript的接口规则。我知道下面的代码块会抛出错误,因为id
属性没有在接口中定义:
interface Person {
name: string;
}
let person: Person = { name: 'Jack', id: 209 }
但是,如果我试图通过分配另一个对象来添加id
属性,为什么TypeScript不会抛出错误呢?例如:
const samePerson = { name: 'Jack', id: 209 };
person = samePerson;
person
对象最终会获得id
属性,即使它没有在接口中定义。
3条答案
按热度按时间4dbbbstv1#
对象类型通常是开放和可扩展的,并允许额外的属性...
TypeScript中的对象类型通常是开放的和可扩展的。一个对象是
Person
当且仅当它具有string
类型的name
属性。这样的对象可能会有额外的属性,但它仍然是Person
。这是非常有用的,并允许接口扩展形成类型层次结构:因为TypeScript有一个结构化类型系统,所以实际上不必为
AgedPerson
声明一个接口,就可以将其视为Person
的子类型:这里,
undeclaredButStillAgedPerson
的类型为{name: string, age: number}
,等价于AgedPerson
,并且基于同样的原因,对Person
的后续赋值也是有效的。尽管开放/可扩展类型是有用的,但它可能会令人困惑,有时候并不是所期望的。在microsoft/TypeScript#12936上有一个长期开放的请求,要求TypeScript支持所谓的 exact 类型,其中类似
Exact<Person>
的内容将 * 仅 * 被允许具有name
属性,而不允许具有其他属性。AgedPerson
应该是Person
,但不是Exact<Person>
。目前还没有直接支持这样的确切类型。...但 * 对象文字 * 会进行额外的属性检查。
跳回:TypeScript中的对象类型 * 通常 * 是开放的。但有一种情况,对象将被视为其类型是精确的。这就是当你将object literal赋给变量或将其作为参数传递时。
对象文字会得到特殊的处理,并且在第一次赋值给变量或作为函数参数传递时会进行额外的属性检查。如果对象文字具有未知的属性,而这些属性并不存在于预期的类型中,则会出现错误。如下所示:
即使
{name: "Jack", id: 209}
* 按照原始定义是Person
,但它不是Exact<Person>
,因此我们得到一个错误。请注意,该错误特别提到了“对象字面值”。将此与下面的代码进行对比,其中没有错误:
将对象文字赋值给
samePerson
没有错误,因为samePerson
的类型被 * 推断 * 为类型并且没有多余的属性。
samePerson
到person
的后续赋值也会成功,因为samePerson
不是对象文字,因此多余的属性检查不适用。Playground代码链接
p5fdfcr12#
"让我们试着去理解"
接口是实体应该遵守的语法契约。
接口定义属性、方法和事件,它们是接口的成员。接口只包含成员的声明。定义成员是派生类的责任。它通常有助于提供派生类将遵循的标准结构
此外,我们可以实现多个接口,这样实现者就可以拥有一个接口定义的多个接口。
虽然类必须遵守契约中的内容,但它也可以有自己的附加实现(这在许多语言中都是如此)
来到 typescript ,就像@zerkms说的
TypeScript中的类型兼容性基于结构化子类型。结构化类型是一种仅基于类型成员来关联类型的方法。
TypeScript的结构类型系统的基本规则是,如果y至少与x具有相同的成员,则x与y兼容。例如:
为了检查dog是否可以赋值给pet,编译器检查pet的每个属性,以在dog中找到对应的兼容属性。在这种情况下,dog必须有一个名为name的成员,该成员是字符串。它确实是,所以允许赋值。
参考:type-compatibility
jogvjijk3#
最后,在typescript 4.9中,使用satisfied运算符修复了这个问题,因此如果您像这样创建类型
它不允许在此对象中使用其他属性,如果您尝试添加它,则会给予错误