In a parent class, I define a method with different parameters: an index in a store or an element, and depending on the parameter, I get the element from the store by its index or just use the given element.
Thus, I use overloading to define these methods:

class Parent<T> {
  items: T[] = [];
  item: T = {} as T;

  applyMethod(index: number): void;
  applyMethod(item: T): void;
  applyMethod(param: number | T): void {
    if (typeof param === 'number') {
      param = this.items[param];
    }
    this.item = param;
  }
}

class Child1<T> extends Parent<T> {
  // In my application, I actually add an id: string overload.
  override applyMethod(param: number | T): void {
    // Do something more before calling:
    super.applyMethod(param);
  }
}

class Child2<T> extends Parent<T> {
  override applyMethod(param: number | T): void {
    if (typeof param === 'number') {
      super.applyMethod(param);
    } else {
      super.applyMethod(param);
    }
  }
}

TS Playground强中也能看到它.

在Child1中,我有一个错误:

No overload matches this call.\
  Overload 1 of 2, '(index: number): void', gave the following error.\
    Argument of type 'number | T' is not assignable to parameter of type 'number'.\
      Type 'T' is not assignable to type 'number'.
  Overload 2 of 2, '(item: T): void', gave the following error.\
    Argument of type 'number | T' is not assignable to parameter of type 'T'.\
      'T' could be instantiated with an arbitrary type which could be unrelated to 'number | T'.(2769

我可以像《子元素2》中所展示的那样解决这个问题,但它非常奇怪/愚蠢.

另一种解决办法是定义applyMethod(param: any): void {},而不是number | T.它是classic 的,这就是我最初所做的,但我发现TS只看到param: any参数定义,因此我输掉了输入.

我 Select 了另一种方式,拨打了super.applyMethod(param as any);.至少,输入保留了它们的类型.

有没有其他更优雅的方式?我错过了什么吗?

推荐答案

TypeScript中的Overloads有各种限制,使得它们很难使用. 您遇到的限制是,当前类型判断器仅将调用解析为正好one个调用签名.它不会try combine个呼叫签名.

如果您有两个调用签名,如(x: string) => string(y: number) => number,则可以调用f("")f(0),但不能调用f(Math.random()<0.5?"":0),因为后者需要编译器将这两个调用签名组合成类似(xOrY: string | number) => string | number的东西.也就是说,它将需要合成对应于每个调用签名的unions个参数的调用签名.

microsoft/TypeScript#14107上有一个公开的功能请求,要求提供这样的支持,但目前它不是语言的一部分.


在此之前,除非它被实现,否则您将需要解决它.如果您可以完全避免重载,这通常是最好的方法,因为类型系统有多种方式不能很好地支持重载(可以找到Typescript: ReturnType of overloaded function个和许多其他示例).

如果您确实希望保留重载,则解决方法之一(如GitHub问题中所述)是手动添加包含相关联合的重载(在底部):

class Parent<T> {
  items: T[] = [];
  item: T = {} as T;

  applyMethod(index: number): void;
  applyMethod(item: T): void;
  applyMethod(param: number | T): void; // <-- add catch-all overload
  applyMethod(param: number | T): void {
    if (typeof param === 'number') {
      param = this.items[param];
    }
    this.item = param;
  }
}

现在,子类内部的调用按编写的方式工作,因为它可以解析为来自父类的第三个调用签名:

class Child1<T> extends Parent<T> {
  override applyMethod(param: number | T): void {
    super.applyMethod(param); // okay
  }
}

Playground link to code

Typescript相关问答推荐

TS不推断类型在条件判断后具有属性'

将值添加到具有不同类型的对象的元素

有没有办法解决这个问题,类型和IntrinsicAttributes类型没有相同的属性?

类型脚本`Return;`vs`Return unfined;`

对于合并对象中的可选属性(例如选项),类型推断可能是错误的

如何在LucideAngular 添加自定义图标以及如何使用它们

使用Dockerfile运行Vite React应用程序时访问env变量

TypeScrip:来自先前参数的参数中的计算(computed)属性名称

函数重载中的不正确TS建议

推断从其他类型派生的类型

如何将别名添加到vitest配置文件?

在构建Angular 应用程序时,有没有办法通过CLI传递参数?

Rxjs将省略的可观测对象合并到一个数组中

为什么TypeScrip假定{...省略类型,缺少类型,...选取类型,添加&>}为省略类型,缺少类型?

有没有什么方法可以使用ProvideState()提供多个削减器作为根减减器

如何调整项目数组,但允许权重影响顺序

可以将JS文件放在tsconfig';s includes/files属性?或者,我如何让tsc判断TS项目中的JS文件?

断言同级为true或抛出错误的Typescript函数

TS 条件类型无法识别条件参数

使用 Next.js 路由重定向到原始 URL 不起作用