TypeScript 中的对象类型通常不禁止额外的属性。它们是“开放的”或“可扩展的”,而不是“封闭的”或“精确的”(参见microsoft/TypeScript#12936)。否则无法使用子类或接口扩展:
interface FooWidget extends Widget {
foo: string;
}
const f: FooWidget = { creationTime: 123, foo: "baz" };
const w: Widget = f; // okay
有时人们想要这样的“精确”类型,但它们并不是语言的一部分。相反,TypeScript 拥有的是excess property checking,它只发生在非常特殊的情况下:当一个“新鲜”的对象字面量被赋予一个不知道对象字面量中某些属性的类型时:
const x: Widget = { creationTime: 123, foo: "baz" }; // error, what's foo
一个对象字面量是“新鲜的”,如果它还没有被分配给任何类型。 x 和 w 之间的唯一区别是,在 x 中,文字是“新鲜的”并且禁止使用过多的属性,而在 w 中,文字是......呃......“陈旧”,因为它有已经被赋予类型FooWidget。
由此看来,您的 widgetFactory 似乎应该给出一个错误,因为您正在返回对象字面量而没有在任何地方分配它。不幸的是,在这种情况下失去了新鲜感。有一个长期存在的问题,microsoft/TypeScript#12632,注意到了这一点,并且取决于一个非常老的问题,microsoft/TypeScript#241。 TypeScript 在检查它是否与预期的返回类型兼容时会自动扩展返回的类型......并且新鲜度会丢失。看起来没人喜欢这个,但是it's hard to fix it without breaking other things。所以就目前而言,就是这样。
您已经有了一种解决方法:显式注释函数的返回类型。这不是特别令人满意,但它完成了工作。
export const WidgetFactory1: Factory<Widget> = {
build: (): Widget => {
return {
creationTime: Date.now(),
foo: 'bar', // error!
};
},
};
其他涉及强制编译器计算精确类型的解决方法是可能的,但比你正在做的要丑得多:
const exactWidgetFactory =
<W extends Widget & Record<Exclude<keyof W, keyof Widget>, never>>(
w: Factory<W>) => w;
export const WidgetFactory2 = exactWidgetFactory({
build: () => { // error!
// ~~~~~ <-- types of property foo are incompatible
return {
creationTime: Date.now(),
foo: 'bar',
};
},
});
所以我建议您继续使用现有的内容。
好的,希望对您有所帮助;祝你好运!
Playground link to code