重载签名、联合类型和“没有重载与此调用匹配”错误

2024-06-05

想象一个 TypeScript (4.2.2) 函数接收一个string or a Promise<string>并使用最初收到的相同类型返回答案,例如:

function trim(textOrPromise) {
    if (textOrPromise.then) {
        return textOrPromise.then(value => result.trim());
    }
    return textOrPromise.trim();
}

我想使用泛型定义此类函数的签名来指示传入Promise总是会导致Promise,并传入一个string结果是string

为此,我定义了以下重载:

function trim(text: Promise<string>): Promise<string>
function trim(text: string): string
function trim(text: any): any {
    if (text.then) {
        return text.then(result => result.trim());  // returns Promise<string>
    }
    return text.trim();                             // returns string
}

只要参数明确定义为,就可以正确转换string or a Promise<string>:

let value: string
trim(value)                          // fine
let value: Promise<string>
trim(value)                          // fine

但是,当我使用 a 定义参数时联合型 https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html (Promise<string> | string):

let value: Promise<string> | string
trim(value)                          // error: TS2769

我收到以下转换错误:

TS2769: No overload matches this call.
   Overload 1 of 2, '(text: Promise<string>): Promise<string>', gave the following error.
    Argument of type 'string | Promise<string>' is not assignable to parameter of type 'Promise<string>'.
       Type 'string' is not assignable to type 'Promise<string>'.
   Overload 2 of 2, '(text: string): string', gave the following error.
     Argument of type 'string | Promise<string>' is not assignable to parameter of type 'string'.
       Type 'Promise<string>' is not assignable to type 'string'.

有趣的是,当我将联合类型添加到函数签名时,该示例会正确转换并表现正常

function trim(text: Promise<string>): Promise<string>
function trim(text: string): string
function trim(text: Promise<string> | string): Promise<string> | string
function trim(text: any): any {
    if (text.then) {
        return text.then(result => result.trim());
    }
    return text.trim();
}

let value: Promise<string> | string
trim(value)                          // fine

通过最后一个实现,TypeScript 知道当函数接收到Promise它将返回一个Promise,当它收到一个string它返回一个string。与联盟相反Promise<string> | string正如第三次过载所暗示的那样。

如果有人能够解释这种行为以及必须为联合类型添加重载的原因,我将不胜感激。


将联合传递给重载

在根据您的重载签名进行检查之前,Typescript 无法“拆分”联合。它正在检查你的变量类型Promise<string> | string分别针对每个过载。联合体不可分配给其任何一个成员,因此不存在接受的重载签名Promise<string> | string.

这是一种已知的行为,并且有关此问题的一些 GitHub 问题可以追溯到几年前。

  • 支持使用类型联合参数的重载解析 #14107 https://github.com/microsoft/TypeScript/issues/14107
  • 联合类型不适用于旧式重载 #1805 https://github.com/microsoft/TypeScript/issues/1805

反对更改打字稿行为的论点似乎是,当您的函数具有多个参数且每个参数接受多种类型时,可能的组合数量可能会迅速激增。

由于打字稿本身不支持这一点,因此您必须手动添加一个接受并集并返回并集的重载,就像您已经完成的那样。请注意,实现签名(重载的最后一行)不算作重载之一。因此,仅在实现签名中包含并集是不够的。

function trim(text: Promise<string>): Promise<string>;
function trim(text: string): string;
function trim(text: Promise<string> | string): Promise<string> | string;
function trim(text: Promise<string> | string): Promise<string> | string {
  if (typeof text === "string") {
    return text.trim();
  } else {
    return text.then(result => result.trim());
  }
}

Generics

当使用泛型而不是重载时,我们不会遇到这个问题,因为extends包括工会。T extends A | B意思是T can be A, B,或工会A | B(或者任何更精致的版本,我稍后会介绍)。

function trim<T extends Promise<string> | string>(text: T): T {

然而,当涉及到函数的实现时,泛型会带来自己的问题,因为改进了变量的类型text没有细化泛型的类型T。考虑到类型,这种行为一般来说是有意义的T可以是一个工会。

这里令人沮丧,尤其是因为我们返回与输入相同的类型。所以如果我们知道text is a string那么我们知道T必须包括string所以返回一个应该没问题string, 正确的?没有。

感觉我们只有三种可能(string, Promise<string>, Promise<string> | string). But extends打开无限可能的类型T这是这些的改进。如果T是文字字符串" A "那么string "A"我们从text.trim() really isn't可分配给T。因此,我们解决了一个问题,但又产生了另一个问题。我们必须做as断言以避免以下错误:

类型“string”不可分配给类型“T”。 'string' 可分配给类型'T' 的约束,但'T' 可以使用约束'string | 的不同子类型来实例化。承诺'。(2322)

在一些奇怪的边缘情况下,这些断言可能是不正确的。

function trim<T extends Promise<string> | string>(text: T): T {
  if (text instanceof Promise) {
    return text.then((result) => result.trim()) as T;
  }
  return (text as string).trim() as T;
}

const a = trim(' ' as Promise<string> | string);  // type: string | Promise<string>
const b = trim(' ');                              // type: ' ' -- actually wrong!
const c = trim(' ' as string);                    // type: string
const d = trim(new Promise<string>(() => ' '));   // type: Promise<string>
const e = trim(new Promise<' '>(() => ' '));      // type: Promise<' '> -- wrong again!

Typescript Playground 链接 https://tsplay.dev/Nr2vDN

所以我更喜欢重载版本,即使你需要那条额外的线。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

重载签名、联合类型和“没有重载与此调用匹配”错误 的相关文章

随机推荐