typescript - 重载签名、联合类型和 "No overload matches this call"错误

标签 typescript typescript-generics

想象一个 TypeScript (4.2.2) 函数接收 stringPromise<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 就可以正确转换或 Promise<string> :
let value: string
trim(value)                          // fine
let value: Promise<string>
trim(value)                          // fine
但是,当我使用 union type 定义参数时( 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 问题。

  • Support overload resolution with type union arguments #14107
  • Union types not working with old-style overloads #1805

  • 反对改变 typescript 行为的论点似乎是,当您有一个具有多个参数且每个参数都接受多种类型的函数时,可能的组合数量可能会迅速增加。
    由于 typescript 本身不支持这一点,因此您必须手动添加一个接受联合并返回联合的重载,就像您已经完成的那样。请注意,实现签名(重载的最后一行)不算作重载之一。因此,仅在实现签名中使用联合是不够的。
    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());
      }
    }
    
    泛型
    我们在使用泛型而不是重载时没有这个问题,因为 extends包括工会。 T extends A | B意味着 T可以 A , B ,或工会 A | B (或这些的任何更精致的版本,我稍后会介绍)。
    function trim<T extends Promise<string> | string>(text: T): T {
    
    然而,泛型在函数的实现方面会提出自己的问题,因为改进了变量 text 的类型。不细化泛型 T 的类型.考虑到类型 T,这种行为通常是有意义的。可以是工会。
    在这里令人沮丧,特别是因为我们返回与输入相同的类型。所以如果我们知道 textstring那么我们知道T必须包括 string所以返回 string 应该没问题, 对?不。
    感觉我们只有三种可能性( stringPromise<string>Promise<string> | string )。但是extendsT 打开无限可能的类型那是那些的改进。如 T是文字串 " A "然后是 string "A"我们从 text.trim() 返回真的不能分配给 T .所以我们解决了一个问题,但又创造了另一个问题。我们必须做 as断言以避免错误,例如:

    Type 'string' is not assignable to type 'T'. 'string' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string | Promise'.(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 Link
    所以我更喜欢重载版本,即使你需要额外的行。

    关于typescript - 重载签名、联合类型和 "No overload matches this call"错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66507585/

    相关文章:

    angular - 与 ngIf 绑定(bind)

    typescript - TS-Jest 无法解析 tsconfig 路径

    angular - 在 Angular 2、RxJS 中重用代码——订阅者体内的重复代码

    typescript - 具有属性的类,其中属性本身包含 <T extends Class> 类型的属性

    typescript - 没有泛型的类型推断?

    javascript - 比较 JavaScript 中的对象

    jquery - 如何在 typescript 中使用 JQuery 的每个函数

    typescript - 如何修复 "Type ' BaseCtor' 不可分配给类型 'T'“同时使用带有默认参数的泛型

    typescript - 函数返回类型不可分配给泛型解析的内容

    typescript - 推断重载函数的参数