这是一个非常基本的示例来说明我的意思:
type Payload = {
id: number;
}
type GreatPayload = {
id: number;
surprise: 4;
}
type Action = (payload: Payload) => void;
const action: Action = payload => null;
const payload: GreatPayload = {
id: 1,
surprise: 4,
};
action({ id: 1, surprise: 4 }); // <== as expected, this errors out because `surprise` is not present in `Payload`
action(payload); // <== my question is, why does this not throw an error?
(还有TypeScript playground link为可编辑示例。)
当传入的action(payload)
类型显然与函数参数类型payload
不匹配时,为什么GreatPayload
不会引发错误?
答案 0 :(得分:6)
TypeScript中的对象类型是开放的/可扩展的,不是封闭的/ exact。这意味着类型为X
的对象包含比X
的定义更多的属性是可以接受的。您可以将对象类型定义视为描述该类型的已知属性,而对可能的未知属性没有任何意义。
这种开放性很重要,因为它允许接口扩展和类继承。您的类型定义几乎与
相同interface Payload {
id: number;
}
interface GreatPayload extends Payload {
surprise: 4;
}
在这里您可以看到GreatPayload
是的一种特殊类型的Payload
。它具有额外的属性,但仍然是Payload
。与类继承相同:
class Foo {
a = "foo";
}
class Bar extends Foo {
b = "bar";
}
Bar
实例是一个Foo
:
const f: Foo = new Bar(); // okay
TypeScript编译器将对象类型视为正确的唯一地方是在创建全新的对象文字并将其分配给类型时。这在TypeScript手册中记录为“ Excess Property Checks” ...,您也可以查看microsoft/TypeScript#3755,这是讨论此行为需求的GitHub问题。如果不进行此类键检查,则拼写错误的可选属性将是完全无法捕获的错误。但这不是完整类型的完整实现。
因此,当您致电此电话时:
action({ id: 1, surprise: 4 }); // error
您正在传入一个包含意外的surprise
属性的新鲜对象文字,并且编译器会通过过多的属性检查来发出警告。但是当您打电话给这个时候:
action(payload); // okay
您要传入变量payload
,该变量本身不是对象文字,并且您分配给payload
的对象文字不再是“新鲜”的。因此,不会进行多余的财产检查,也不会收到警告。
如果您真的想查看实现的确切类型,以便可以轻松地请求Exact<Payload>
,则可能需要转到microsoft/TypeScript#12936并加上a,甚至可以描述您的用例尤其引人注目。
但是考虑到当前的行为可能暂时不会出现,因此您最好花时间尝试使用 开放类型,而不是与它们对抗。考虑编写代码,以便它不介意对象的属性是否超过类型声明中指定的属性。如果您只是使用已知键索引到对象中,那将很好。如果要遍历对象属性,则如果代码会在意外的属性上爆炸,请不要使用Object.keys()
或for..in
循环。相反,请考虑遍历硬编码数组中的已知键(执行此操作的一种方法,请参见this answer)。这样做的目的是使您的代码不受未知的额外属性的影响,这样您就不会在有人期望GreatPayload
的情况下是否有人给您Payload
。
好的,希望能有所帮助;祝你好运!
答案 1 :(得分:0)
欢迎Satck溢出!
TS中的类型实际上不是“类型”,而是类型别名。根据{{3}}类型,别名有时类似于接口。
在Typescript中,实现接口的实例可以具有其他属性,并且仍然有效。因此,编译器将 GreetPayload 实例作为输入,不会对您的action
类型引发任何错误。
答案 2 :(得分:-2)
此medium article为您遇到的Typescript行为提供了很好的解释。