Typescript playground link

我试图在子类中扩展重载方法,但Typescript在调用原始函数时似乎没有使用正确的参数类型:

declare class Creep {
    move(direction: number): void;
    move(target: string): void;
}

const _originalMove = Creep.prototype.move;

class CreepExtra extends Creep {
    move(direction: number): void;
    move(target: string): void;
    move(direction: number | string) {
        if (typeof direction === 'string') {
            return _originalMove.call(this, direction);
        }
        return _originalMove.call(this, direction);
    }
}

导致此错误(对于上次调用的direction参数):

Argument of type 'number' is not assignable to parameter of type 'string'.(2345)

是我做错了什么,还是这是打字脚本中的错误?

推荐答案

这是TypeScript的一个限制.对于泛型函数或重载函数,对强类型Function.prototype.call() methods(即introduced in TypeScript 3.2)的支持不起作用.参见microsoft/TypeScript#27028中的this comment,实现该支持的pull请求.

当您直接调用重载函数时,编译器将 Select 最合适的调用签名.但是当你开始处理重载函数types时,编译器基本上会放弃,只 Select 其中一个调用签名,而不注意它是否适合最终的用例.这通常是最后一个通话信号(尽管有时我猜是第一个)它没有很好的记录;有关信息,请参见microsoft/TypeScript#43187.

在您的例子中,这意味着_originalMove上的Function.prototype.call()的行为就好像它只有最后一个(target: string) => void;类型的呼叫签名一样.这对你的一个电话很好,但对另一个电话不好.哦,好吧.


这里的一个解决方法是,在调用call()之前,显式地将_originalMove的类型扩展到单个相关的调用签名.也就是说,以下被视为安全任务:

  const om: (direction: number) => void = _originalMove;

因为_originalMove有两个电话签名.此时,om.call()将允许第二个参数为number类型:

  return om.call(this, direction);

一切正常:

class CreepExtra extends Creep {
  move(direction: number): void;
  move(target: string): void;
  move(direction: number | string) {
    if (typeof direction === 'string') {
      return _originalMove.call(this, direction);
    }
    const om: (direction: number) => void = _originalMove;
    return om.call(this, direction); // okay
  }
}

万岁!


请注意:

在这里显示的示例中,重载方法有两个调用签名,它们只不同于单个函数参数的类型;这样的呼叫签名可以通过union types轻松统一:

declare class Creep {
  move(directionOrTarget: number | string): void;
}

const _originalMove = Creep.prototype.move;

class CreepExtra extends Creep {
  move(targetOrDirection: number | string) {
    return _originalMove.call(this, targetOrDirection);
  }
}

因为重载和类型推断并不能很好地结合在一起,如本答案的第一部分所示,如果你有机会在不牺牲任何其他东西的情况下删除重载,那么你应该接受它.您的实际用例可能不像这里显示的示例代码那么简单,但对于每个人来说,判断重载的好处是否大于缺点是一个很好的练习.

Playground link to code

Typescript相关问答推荐

Typescript如何从字符串中提取多个文本

为什么ESLint抱怨通用对象类型?

泛型函数类型验证

角形标题大小写所有单词除外

类型脚本接口索引签名

如何在第一次加载组件时加载ref数组

类型脚本基于数组作为泛型参数展开类型

打字类型保护递归判断

如何将泛型对象作为返回类型添加到类型脚本中

如何将v-for中的迭代器编写为v-if中的字符串?

剧作家元素发现,但继续显示为隐藏

编译TypeScrip项目的一部分导致错误

复选框Angular 在Ngfor中的其他块中重复

TYPE FOR ARRAY,其中每个泛型元素是单独的键类型对,然后在该数组上循环

在Reaction中折叠手风琴

如何在Nextjs路由处理程序中指定响应正文的类型

TS不能自己推断函数返回类型

TypeScript:如何使用泛型和子类型创建泛型默认函数?

T的typeof键的Typescript定义

Typescript 是否可以区分泛型参数 void?