Typescript重写子类中的重载方法-类型不正确



打字游戏场链接

我试图在子类中扩展一个重载方法,但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);
}
}

导致此错误(针对上次调用的方向参数(:

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

是我做错了什么,还是这是打字错误?

这是TypeScript的一个限制。TypeScript 3.2中引入的对强类型Function.prototype.call()方法的支持不适用于泛型或重载函数。请参阅实现此支持的pull请求microsoft/TypeScript#27028中的此注释。

当您直接调用重载函数时,编译器将选择最合适的调用签名。但是,当您开始处理重载函数类型时,编译器基本上会放弃,只选择其中一个调用签名,而不注意它是否适合最终的用例。这通常是最后一个呼叫签名(尽管有时我想是第一个?(它没有很好的记录;有关信息,请参阅microsoft/TypeScript#43187。

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


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

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

因为CCD_ 7具有两个呼叫签名。此时,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
}
}

万岁!


注意:

在这里显示的示例中,您的重载方法有两个调用签名,它们仅不同于单个函数参数的类型;这样的呼叫签名可以很容易地通过联合类型进行统一:

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);
}
}

正如这个答案的第一部分所示,由于重载和类型推理并不能很好地结合在一起,如果你有机会在不牺牲任何其他东西的情况下删除重载,那么你应该接受它。假设你的实际用例并不像这里显示的示例代码那么简单,但对于每个人来说,检查过载的好处是否大于坏处是一个很好的练习。

游乐场链接到代码

最新更新