使用条件返回类型实现泛型函数

bin*_*les 5 typescript

我正在尝试一个非常基本的(人为的)条件类型函数并遇到意外错误:

function test<
  T
>(
  maybeNumber: T
): T extends number ? number : string {
  if (typeof maybeNumber === 'number') {
    return maybeNumber // Type 'T & number' is not assignable to type 'T extends number ? number : string'.
  }

  return 'Not a number' // Type '"Not a number"' is not assignable to type 'T extends number ? number : string'.
}
Run Code Online (Sandbox Code Playgroud)

我认为这是条件类型的非常简单的用法,所以不确定发生了什么。有任何想法吗?

澄清一下,我并不是真的想实现这个特定的功能。我只是在试验条件类型,并想更好地理解为什么这实际上不起作用。

jca*_*alz 6

潜在的问题是 TypeScript 的编译器没有通过控制流分析来缩小泛型类型变量的类型。当您检查时(typeof maybeNumber === "number"),编译器可以将 缩小maybeNumbernumber,但不会将类型参数 缩小Tnumber。因此它无法验证number为返回类型赋值是否安全T extends number ? number : string。编译器将不得不执行一些它目前不做的分析,例如“好吧,如果typeof maybeNumber === "number",我们单独T从 的类型推断maybeNumber,那么在这个块中我们可以缩小Tnumber,因此我们应该返回一个类型的值number extends number ? number : string,也就是,number”。但这不会发生。

对于具有条件返回类型的泛型函数,这是一个相当大的痛点。关于此的规范的开放 GitHub 问题可能是microsoft/TypeScript#33912,但还有许多其他 GitHub 问题,这是主要问题。

所以这就是“为什么这行不通”的答案?


如果您对重构以使其工作不感兴趣,则可以忽略其余部分,但知道在这种情况下该做什么而不是等待语言更改可能仍然具有指导意义。

此处维护您的调用签名的最直接的解决方法是使您的函数成为单个签名重载,其中实现签名不是通用的。这实质上放松了实现内部的类型安全保证:

type MyConditional<T> = T extends number ? number : string;
type Unknown = string | number | boolean | {} | null | undefined;

function test<T>(maybeNumber: T): MyConditional<T>;
function test(maybeNumber: Unknown): MyConditional<Unknown> {
  if (typeof maybeNumber === 'number') {
    const ret: MyConditional<typeof maybeNumber> = maybeNumber;
    return ret;
  }
  const ret: MyConditional<typeof maybeNumber> = "Not a number";
  return ret;
}
Run Code Online (Sandbox Code Playgroud)

在这里,我尽可能地尝试保证类型安全,方法是使用ret注释为 as的临时变量,MyConditional<typeof maybeNumber>该变量使用maybeNumber. 如果您切换周围的检查(反过来,这至少会抱怨===!==验证)。但通常我只是做一些像这样更简单的事情,让筹码掉到他们可能的地方:

function test2<T>(maybeNumber: T): MyConditional<T>;
function test2(maybeNumber: any): string | number {
  if (typeof maybeNumber === 'number') {
    return maybeNumber;
  }
  return "Not a number";
}
Run Code Online (Sandbox Code Playgroud)

好的,希望有帮助;祝你好运!

Playground 链接到代码

  • 非常好的答案,我不知道存在带有一个附加调用签名的重载的用例。奇怪的是,TS 文档如此强烈地声明:“*实现*的签名从外部是不可见的。在编写重载函数时,**您应该始终在函数的实现之上有*两个*或更多签名** ”。 (2认同)