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

标签 typescript

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

最佳答案

Excess property checking仅在特定情况下才会发生,而您已经找到了不会发生的情况。 TypeScript 中的类型(包括 Foo 等接口(interface))是开放式和可扩展的;你可以定义一个接口(interface),比如

interface Baz extends Foo {
  baz: string;
}

并在 bar() 回调中返回一个 Baz,它是一个有效的 Foo:

const baz: Baz = { foo: "abc", baz: "def" };
const foo: Foo = baz; // no error
const z = bar(() => baz); // no error... 

TypeScript 没有对应于 exactly 的具体类型Foo 没有额外的属性。但是,可以使用 generic constraint表示“没有已知额外属性的Foo”。请注意“已知”警告,那里...稍后会出现:

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 类型的值。编译器只知道或关心 fooFoo 类型。它完全忘记了 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

关于typescript - 防止回调返回类型在 TypeScript 中包含其他属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57316443/

相关文章:

reactjs - 如何将函数作为参数传递给 TypeScript 中的 ReactJS 组件

typescript - 缩写冗长的 TypeScript 类型

javascript - 根据属性的值将对象数组动态拆分为组

typescript - 在 vue3 中使用 headless ui 的 Modal

typescript - 为什么两个接口(interface)的联合与联合的接口(interface)不同(具体涉及我下面的示例)?

typescript - 接口(interface)中的通配符属性名称?

typescript - 在 TypeScript 中对非类型化数据强制类型化

typescript - 来自 Pressable TypeScript 错误的自定义 TouchableOpacity

visual-studio - TypeScript/Visual Studio 2012/编译参数

typescript - 一个类只能实现一个对象类型或对象类型与静态已知成员的交集