想象一个 TypeScript (4.2.2) 函数接收 string
或 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
就可以正确转换或 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 问题。
反对改变 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
,这种行为通常是有意义的。可以是工会。在这里令人沮丧,特别是因为我们返回与输入相同的类型。所以如果我们知道
text
是 string
那么我们知道T
必须包括 string
所以返回 string
应该没问题, 对?不。感觉我们只有三种可能性(
string
、 Promise<string>
、 Promise<string> | string
)。但是extends
为 T
打开无限可能的类型那是那些的改进。如 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/