为什么 Typescript 不要求我的函数返回某种类型?

Rob*_*bin 5 javascript typescript

我有一个Factory应该返回特定类型的通用函数:

type Factory<T> = () => T;

interface Widget {
  creationTime: number;
}

const build: Factory<Widget> = () => {
  return {
    creationTime: Date.now(),
    foo: 'bar',
  };
};
Run Code Online (Sandbox Code Playgroud)

我希望 Typescript 抛出错误,因为 foo它不是接口 Widget 上的属性。然而,事实并非如此。

但是如果我将widgetFactory函数修改为下面的代码——唯一的区别是我明确声明了返回类型——那么它确实会抛出一个错误:

const build: Factory<Widget> = (): Widget => {
  return {
    creationTime: Date.now(),
    foo: 'bar',
  };
};
Run Code Online (Sandbox Code Playgroud)

有没有办法让 Typescript 为我的泛型Factory类型分配相同的“严格性” ?

jca*_*alz 6

TypeScript 中的对象类型通常不会禁止额外的属性。它们是“开放的”或“可扩展的”,而不是“封闭的”或“精确的”(请参阅​​ microsoft/TypeScript#12936)。否则就不可能使用子类或接口扩展:

interface FooWidget extends Widget {
   foo: string;
}
const f: FooWidget = { creationTime: 123, foo: "baz" };
const w: Widget = f; // okay
Run Code Online (Sandbox Code Playgroud)

有时人们想要这种“精确”的类型,但它们实际上并不是语言的一部分。相反,TypeScript 拥有的是过多的属性检查,这只发生在非常特殊的情况下:当给“新鲜”对象字面量一个不知道对象字面量中某些属性的类型时:

const x: Widget = { creationTime: 123, foo: "baz" }; // error, what's foo
Run Code Online (Sandbox Code Playgroud)

如果对象文字尚未分配给任何类型,则它是“新鲜的”。x和之间的唯一区别w是,在x字面上是“新鲜”的,并且禁止使用多余的属性,而在w字面上是...呃...“陈旧”,因为它已经被赋予了类型FooWidget


由此看来,您似乎widgetFactory应该给出错误,因为您返回对象文字而不将其分配到任何地方。不幸的是,在这种情况下,新鲜感就消失了。有一个长期存在的问题microsoft/TypeScript#12632注意到了这一点,并且依赖于一个非常老的问题microsoft/TypeScript#241。当检查返回类型是否与预期返回类型兼容时,TypeScript 会自动加宽返回类型……并且新鲜感就会丢失。看起来没人喜欢这个,但是在不破坏其他东西的情况下很难修复它。所以目前来说,就是这样。


您已经有了一种解决方法:显式注释函数的返回类型。这并不是特别令人满意,但它完成了工作。

export const WidgetFactory1: Factory<Widget> = {
   build: (): Widget => {
      return {
         creationTime: Date.now(),
         foo: 'bar', // error!
      };
   },
};
Run Code Online (Sandbox Code Playgroud)

其他涉及尝试强制编译器计算精确类型的解决方法也是可能的,但比您正在做的要丑陋得多:

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',
      };
   },
});
Run Code Online (Sandbox Code Playgroud)

所以我建议继续你现有的事情。

Playground 代码链接