使用重载函数处理联合,该重载函数不能在单个重载内处理整个联合

问题描述

我创建了一个带有一个参数的重载函数,并试图在通过各种重载“完全覆盖”的联合中调用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:没有过载与该调用匹配。

重载1,共2个,'((id:number):void'),出现以下错误。类型'number |的参数number []'不可分配给'number'类型的参数。类型'number []'不能分配给类型'number'。
重载2之2,'((ids:number []):void')给出以下错误。类型'number |的参数number []'不可分配给'number []'类型的参数。不能将“数字”类型分配给“数字[]”类型。

TS似乎希望单个重载覆盖整个联合-我如何使我的重载正常工作?

解决方法

TypeScript不会自动将多个调用签名合成为单个签名,这些签名采用来自不同调用签名的参数的并集。有一个公开的问题建议这样做:microsoft/TypeScript#14107,但是到目前为止,发生的事情还很少。有人将不得不提出一种实现方案,该方案不会降低编译器性能或破坏实际代码。

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

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),因为编译器不理解如何验证返回值是否可分配给通用条件类型。对此也有一个未解决的问题,microsoft/TypeScript#33912


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


Playground link to code