使用不处理单个重载中的整个联合的重载函数处理联合

Handling union with an overloaded function that doesn't handle the whole union within a single overload

我创建了一个带有一个参数的重载函数,并尝试在一个被各种重载“完全覆盖”的联合中调用 pass - 就像这样:

function overloaded(id: number): number;
function overloaded(ids: number[]): boolean;
function overloaded(idOrIds: number | number[]): number | boolean {
  if (Array.isArray(idOrIds)) return 0;
  return false;
}

function caller(idOrIds: number | number[]) {
  return overloaded(idOrIds);
}

但是我在调​​用 overloaded 时出现以下错误:

TS2769: No overload matches this call.

Overload 1 of 2, '(id: number): void', gave the following error.     Argument of type 'number | number[]' is not assignable to parameter of type 'number'.       Type 'number[]' is not assignable to type 'number'.
Overload 2 of 2, '(ids: number[]): void', gave the following error.     Argument of type 'number | number[]' is not assignable to parameter of type 'number[]'.       Type 'number' is not assignable to type 'number[]'.

看起来 TS 想要一个单一的重载来覆盖整个联合 - 我怎样才能让我的重载工作?

TypeScript 不会自动将多个调用签名合成为单个签名,这些签名采用来自不同调用签名的参数联合。有一个悬而未决的问题表明:microsoft/TypeScript#14107,但到目前为止还没有发生太多事情。有人必须提出一个不会降低编译器性能或破坏 real-world 代码的实现。

您可以自己提供所需的联合调用签名,如下所示:

function overloaded(id: number): number;
function overloaded(ids: number[]): boolean;
function overloaded(idOrIds: number | number[]): number | boolean; // add this
function overloaded(idOrIds: number | number[]): number | boolean {
    if (Array.isArray(idOrIds)) return 0;
    return false;
}

function caller(idOrIds: number | number[]) {
    return overloaded(idOrIds); // okay
}

或者,您可以将多个调用签名合并为一个通用 conditional 调用签名:

function genericConditional<T extends number | number[]>(
  idOrIds: T
): T extends number ? number : boolean;
function genericConditional(idOrIds: number | number[]): number | boolean {
    if (Array.isArray(idOrIds)) return 0;
    return false;
}

const n = genericConditional(123); // number
const b = genericConditional([123]); // boolean
const nb = genericConditional(Math.random() < 0.5 ? 123 : [123]); // number | boolean

请注意,不幸的是,通用条件仍然需要将实现与调用签名分开(通过重载或通过 type assertion) because the compiler does not understand how to verify that a return value is assignable to a generic conditional type. There's an open issue for that too, microsoft/TypeScript#33912


如果您只有几个重载,那么额外的重载就可以了。但是随着重载数量的增加,添加调用签名来处理它们的每个子集变得站不住脚了。在这些情况下,您会发现单个通用条件调用签名的扩展性会更好。


Playground link to code