在 TypeScript 中,Omit 是如何在编译时被检查的?
我无法理解为什么在使用 Omit 工具类型时这段代码能正常工作:
type Foo = {
prop1: string;
prop2: string;
}
const foo: Foo = {
prop1: 'prop1',
prop2: 'prop2'
}
console.log(foo) // {"prop1": "prop1", "prop2": "prop2"}
type Bar = Omit<Foo, 'prop2'> & {
prop3: string;
}
const bar: Bar = {
...foo,
prop3: 'prop3'
}
console.log(bar) // {"prop1": "prop1", "prop2": "prop2", "prop3": "prop3"}
同时,直接使用 Bar 会如预期般失败:
const qux: Bar = {
prop1: "prop1",
prop2: "prop2", // '{ prop1: string; prop2: string; prop3: string; }' is not assignable to type 'Bar'
prop3: "prop3"
}
答案#1
这是因为多余属性检查仅发生在对象字面量的属性上,并且仅在某些情况下进行。多余属性并不是真正的类型安全问题;编译器只有在认为代码很可能会立即忘记它们时才会发出警告。类似 const x: {a: string}={a: "", b: ""} 的情况是一个问题,因为没有任何东西能够安全地访问该 b 属性。但 const y={a: "", b: ""}; const x: {a: string}=y; 是可以的,因为即使 x 忘记了它,y 仍然记得 b。
再次,一般来说,额外的属性不是类型安全问题。你希望编译器允许额外属性,否则接口扩展就无法工作(如果interface Foo {a: string}和interface Bar extends Foo {b: string},那么禁止额外属性意味着Bar与Foo不兼容,而且 TypeScript 的结构类型系统将被破坏)。TypeScript 并不像 Flow 那样真正拥有“精确类型”。在 microsoft/TypeScript#12936 上有一个关于支持精确类型的开放请求,但目前我们还没有。我们所拥有的只是结构子类型,其中{a: string, b: string}可以赋值给{a: string},以及在非常特定的情况下可能发出警告的额外属性检查。
在 bar 的初始化器中,你正在展开 foo,它是一个已经存在的变量。它不是对象字面量,因此没有多余属性检查。根据 ms/TS#41237,这按预期工作。
另一方面,你为 qux 的初始化器直接在对象字面量中添加了多余的属性,这会发出警告,因为不会有人记得 qux 有一个 prop2 属性。