TypeScript 重载没有选择预期的重载

TypeScript overloads doesn't chose the expected one

假设我在下面有一些代码:


type Func1<T1> = (arg1: T1) => any;

type FuncArray<T1> = (arg1: T1, b: string) => any

interface Callable {
    <T>(fn: Func1<T>, arg1: T): string
    <T1>(fn: FuncArray<T1>, arg1: T1): number
}

const call: Callable = (...args: any): any => { }

interface Config {
    url?: string
    headers?: { [K: string]: string | number }
}

interface Requests {
    (config: Config): string
    (url: string, config?: Config): string
}

const request: Requests = (url: string | Config, config?: Config) => '123'

const h = call(request, {}) // error: Argument of type 'Requests' is not assignable to parameter of type 'FuncArray<string>'.
  // Types of parameters 'config' and 'arg1' are incompatible.
    // Type 'string' has no properties in common with type 'Config'.

错误表明 call 正在使用它的第二个重载签名,我认为它会在这种特定情况下使用它的第一个重载。

我的理解是request应该匹配Requests先重载,然后call(request, {})匹配Callable先重载,但实际上并没有。我哪里弄错了?

所以我的问题是为什么call(request, {})不匹配<T>(fn: Func1<T>, arg1: T): string

此问题与 Callable 的重载无关。您可以通过注释掉第二个重载来亲眼看到:

interface Callable {
    <T>(fn: Func1<T>, arg1: T): string
    //<T1>(fn: FuncArray<T1>, arg1: T1): number
}

然后你看到错误:

const h = call(request, {}) // error!
// -------------------> ~~
// Argument of type '{}' is not assignable to parameter of type 'string'.

所以编译器查看了 request 并推断出 Tstring 类型,然后 {}string 不匹配,你得到一个错误。所以问题是编译器不接受 call(request, {}) 作为 Callable 的第一次重载的匹配项。

如果您取消注释 Callable 的第二个重载,编译器会发现它也不匹配第二个重载,并且错误会更改为 "no overload matches this call"。所以我们不用担心 FuncArray.


那么为什么 call(request, {}) 不匹配第一个 Callable 重载?

问题是Requests是重载函数接口,而generic type parameter inference cannot do overload resolution at the same time。这是 TypeScript 的设计限制。当编译器看到 call(request, {}) 时,它必须推断出 T 的类型。与其尝试确定它应该尝试匹配 Requests 的两个重载中的哪一个, 它只是选择最后一个 (url: string, config?: Config)=> string 匹配 Func1<string>。从那里一切都出错了。


那你能做什么?最简单的方法是手动指定泛型参数以减轻编译器推断它的负担:

const h = call<Config>(request, {}) // okay

一旦你指定 TConfig,编译器 然后 可以对 request 进行重载解析并验证,是的, request 是有效的 Func1<Config>。同样,您可以通过类型断言将 request 的类型扩展为 Func1<Config>,以便正确推断出 T

const i = call(request as Func1<Config>, {}); // okay

任何一种方法都应该有效。


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

Playground link to code