在 Typescript 中 returning 枚举成员时缺少隐式 return 类型

Missing Implicit return type when returning an Enum member in Typescript

今天我在工作中遇到了一些有趣的事情,关于使用 Enum 时函数的 return 类型。我试图在这里重现并制作一个简单的例子。

const enum Fruits {
  Apple,
  Orange,
  Kiwi,
}

function getTest1() {
  return Fruits.Apple;
}

function getTest2(foo: boolean) {
  if (foo) {
    return Fruits.Orange;
  }

  return Fruits.Kiwi;
}

type test1ReturnType = ReturnType<typeof getTest1>;
type test2ReturnType = ReturnType<typeof getTest2>;

您也可以在此处试用此示例:TS Playground

如您所见,我希望 test1ReturnTypeFruits.Apple,因为 test2ReturnType 被正确键入为 Fruits.Orange | Fruits.Kiwi 的并集。事实并非如此,实际上是returning Fruits,这完全没有意义。

为什么 return 不是显而易见的?我知道在 getTest1 fn 中添加 as const 将如何解决这个问题,但它不应该,因为 getTest2 已正确键入。 我想了解发生了什么。

这里的根本问题并不特定于 enums。每当您 return 此类类型的单个 literal type from a function instead of a union 的值时,它就会发生。例如,这是字符串文字的类似行为:

function foo() { return "a"; }
// function foo(): string

function bar() { return Math.random() < 0.5 ? "a" : "z" };
// function bar(): "a" | "z"

编译器使用试探法来确定是否将文字扩展为其基类型,基于什么行为最准确地符合现实世界代码开发人员的期望和愿望的假设。 microsoft/TypeScript#10676 中描述了这些规则,实现此功能的 PR。

例如,比较以下行中的推断类型:

const x = "a"; // const x: "a"
let y = "a"; // let y: string

这里x的类型被推断为字符串文字类型"a",而y的类型从"a"扩展为string .这种差异背后的原因是您不能更改 const 变量的值,但可以更改 let 变量的值。假设这段代码的作者可能打算将 y 的值更改为其他 string,否则他们会使用 const.

当然启发只是启发;他们有时会出错。当编译器的推理规则做了一些你不喜欢的事情时,你可以求助于明确表达你的意图,比如使用 type annotation:

let z: "a" = "a"; // let z: "a"

无论如何,您 运行 遇到的具体情况在 this comment 中有描述。编译器假设很少有人故意编写 return 单一文字类型的函数。一个总是 returns "a"Fruits.Apple 的函数实现可能在未来被合理地修改为 return 不同的 string 值或不同的 Fruit 值...否则作者可能不会首先将其设为函数。所以 return 类型被加宽了。但是,如果函数实现 return 是文字值的并集,则作者更有可能对较窄的类型感到满意。

同样,这并不总是人们想要的,但显然已经足够接近,以至于在引入此功能时破坏了最少数量的现实世界代码。如果您对此不满意,您的追索权将再次更加明确,例如 return type annotation:

function baz(): "a" {
    return "a";
}

Playground link to code