防止回调返回类型在TypeScript中包含其他属性

时间:2019-08-01 20:18:19

标签: typescript

假设我有一个接口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

1 个答案:

答案 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,这是不可能的。例如,如果FBaz,则约束为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并加上一个?。但也许上述解决方案足以满足您的用例。

哦,好吧;希望能有所帮助。祝你好运!

Link to code