假设我有一个接口Foo
和一个函数bar
,该函数接受一个返回Foo
的回调。
interface Foo {
foo: string;
}
function bar(callback: () => Foo): Foo {
return callback();
}
调用此函数时,如果将属性添加到foo
以外的返回值中,则编译器不会引发错误。
// throws an error as expected
const x: Foo = { foo: 'abc', baz: 'def' };
// does not throw an error
const y = bar(() => ({ foo: 'abc', baz: 'def' }));
如何确保对象除Foo
中指定的属性外没有其他属性?我尝试使用type
代替interface
,但结果相同。这是TypeScript playground。
答案 0 :(得分:2)
Excess property checking仅在特定情况下发生,而您发现了在特定情况下没有的情况。 TypeScript中的类型(包括诸如Foo
之类的接口)是开放式和可扩展的。您可以定义类似
interface Baz extends Foo {
baz: string;
}
并在Baz
回调内返回一个bar()
,它是有效的Foo
:
const baz: Baz = { foo: "abc", baz: "def" };
const foo: Foo = baz; // no error
const z = bar(() => baz); // no error...
TypeScript没有对应于exactly Foo
的具体类型,没有额外的属性。但是,可以使用表示“ Foo
没有已知额外属性”的generic constraint。请注意“ 已知”警告,在那里……稍后会出现:
function bar<
F extends Foo & { [K in keyof F]: K extends keyof Foo ? Foo[K] : never }
>(callback: () => F): Foo {
return callback();
}
在这里,我将bar()
变成了一个泛型函数,该函数接受类型为() => F
的回调,其中F
被约束为Foo
以及映射对象条件类型。此映射类型采用任何已知的额外键,并将属性类型更改为never
,这是不可能的。例如,如果F
为Baz
,则约束为Foo & { foo: string, baz: never }
。由于Baz
不扩展{ foo: string, baz: never }
,因此,如果您尝试在已知返回bar()
的函数上调用Baz
,则会出现错误:
const y = bar(() => ({ foo: "abc", baz: "def" })); // error, generic constraint failed
// -----------------------------> ~~~
// "string" is not assignable to "never"
const z = bar(() => baz); // error! "string" is not assignable to "never"
// ---------------> ~~~
这至少将阻止人们返回具有额外属性的对象文字。
但是,这不是一个完美的解决方案。之前的变量foo
注释为类型Foo
,但已使用类型Baz
的值进行了初始化。编译器仅知道或关心foo
的类型为Foo
。完全忘记了foo
也是Baz
类型。因此,它不知道foo
具有"baz"
属性,并且不会阻止这种情况:
const oops = bar(() => foo); // still no error...
// foo has a "baz" property but the compiler forgot
// no way around this in TypeScript, sorry
恐怕除了运行时检查外,这里什么也没做。如果您确实想在TypeScript中查看确切的类型,则可能需要转到relevant GitHub issue并加上一个?。但也许上述解决方案足以满足您的用例。
哦,好吧;希望能有所帮助。祝你好运!