如何在 TypeScript 中进行方法重载?

我想达到这样的目标:

class TestClass {
someMethod(stringParameter: string): void {
alert("Variant #1: stringParameter = " + stringParameter);
}
    

someMethod(numberParameter: number, stringParameter: string): void {
alert("Variant #2: numberParameter = " + numberParameter + ", stringParameter = " + stringParameter);
}
}


var testClass = new TestClass();
testClass.someMethod("string for v#1");
testClass.someMethod(12345, "string for v#2");

下面是我不想做的一个例子(我真的很讨厌在 JS 中重载 hack 的那一部分) :

class TestClass {
private someMethod_Overload_string(stringParameter: string): void {
// A lot of code could be here... I don't want to mix it with switch or if statement in general function
alert("Variant #1: stringParameter = " + stringParameter);
}
    

private someMethod_Overload_number_string(numberParameter: number, stringParameter: string): void {
alert("Variant #2: numberParameter = " + numberParameter + ", stringParameter = " + stringParameter);
}
    

private someMethod_Overload_string_number(stringParameter: string, numberParameter: number): void {
alert("Variant #3: stringParameter = " + stringParameter + ", numberParameter = " + numberParameter);
}
    

public someMethod(stringParameter: string): void;
public someMethod(numberParameter: number, stringParameter: string): void;
public someMethod(stringParameter: string, numberParameter: number): void;


public someMethod(): void {
switch (arguments.length) {
case 1:
if(typeof arguments[0] == "string") {
this.someMethod_Overload_string(arguments[0]);
return;
}
return; // Unreachable area for this case, unnecessary return statement
case 2:
if ((typeof arguments[0] == "number") &&
(typeof arguments[1] == "string")) {
this.someMethod_Overload_number_string(arguments[0], arguments[1]);
}
else if ((typeof arguments[0] == "string") &&
(typeof arguments[1] == "number")) {
this.someMethod_Overload_string_number(arguments[0], arguments[1]);
}
return; // Unreachable area for this case, unnecessary return statement
}
}
}




var testClass = new TestClass();
testClass.someMethod("string for v#1");
testClass.someMethod(12345, "string for v#2");
testClass.someMethod("string for v#3", 54321);

如何使用 TypeScript 语言进行方法重载?

109127 次浏览

最新消息。TypeScript 中的方法重载是一个有用的特性,因为它允许您使用需要表示的 API 为现有库创建类型定义。

不过,在编写自己的代码时,您可以使用可选参数或默认参数避免重载带来的认知开销。这是方法重载更易读的替代方法,并且可以保持 API 的诚实性,因为您可以避免使用不直观的排序创建重载。

TypeScript 重载的一般规律是:

如果可以删除重载签名并且所有测试都通过,则不需要 TypeScript 重载

您通常可以使用可选参数或默认参数来实现同样的功能,或者使用联合类型,或者使用一点面向对象的功能。

真正的问题

实际的问题需要超负荷的工作量:

someMethod(stringParameter: string): void {


someMethod(numberParameter: number, stringParameter: string): void {

现在,即使在支持使用单独实现进行重载的语言中(注意: TypeScript 重载共享单个实现) ,程序员也被建议在排序方面提供一致性。这将使签名:

someMethod(stringParameter: string): void {


someMethod(stringParameter: string, numberParameter: number): void {

stringParameter总是必需的,所以它是第一个。你可以把它写成一个可以工作的 TypeScript 重载:

someMethod(stringParameter: string): void;
someMethod(stringParameter: string, numberParameter: number): void;
someMethod(stringParameter: string, numberParameter?: number): void {
if (numberParameter != null) {
// The number parameter is present...
}
}

但是根据 TypeScript 重载法则,我们可以删除重载签名,所有测试仍然会通过。

someMethod(stringParameter: string, numberParameter?: number): void {
if (numberParameter != null) {
// The number parameter is present...
}
}

实际问题,按实际顺序

如果您决定坚持原始顺序,则重载将是:

someMethod(stringParameter: string): void;
someMethod(numberParameter: number, stringParameter: string): void;
someMethod(a: string | number, b?: string | number): void {
let stringParameter: string;
let numberParameter: number;


if (typeof a === 'string') {
stringParameter = a;
} else {
numberParameter = a;


if (typeof b === 'string') {
stringParameter = b;
}
}
}

现在需要进行大量的分支来确定参数的放置位置,但是如果您已经阅读了这么多内容,那么您确实希望保留这个顺序... ... 但是等等,如果我们应用 TypeScript 重载法则会发生什么呢?

someMethod(a: string | number, b?: string | number): void {
let stringParameter: string;
let numberParameter: number;


if (typeof a === 'string') {
stringParameter = a;
} else {
numberParameter = a;


if (typeof b === 'string') {
stringParameter = b;
}
}
}

分支已经够多了

当然,考虑到我们需要进行大量的类型检查... ... 也许最好的答案就是简单地使用两种方法:

someMethod(stringParameter: string): void {
this.someOtherMethod(0, stringParameter);
}


someOtherMethod(numberParameter: number, stringParameter: string): void {
//...
}

根据规范,TypeScript 确实支持方法重载,但是它非常笨拙,并且包含许多手动检查参数类型的工作。我认为这主要是因为在普通的 JavaScript 中,最接近方法重载的方法包括检查,TypeScript 试图不修改实际的方法主体,以避免任何不必要的运行时性能开销。

如果我理解正确的话,您必须首先为每个重载编写一个方法声明,然后 方法实现检查它的参数以确定调用了哪个重载。实现的签名必须与所有重载兼容。

class TestClass {
someMethod(stringParameter: string): void;
someMethod(numberParameter: number, stringParameter: string): void;


someMethod(stringOrNumberParameter: any, stringParameter?: string): void {
if (stringOrNumberParameter && typeof stringOrNumberParameter == "number")
alert("Variant #2: numberParameter = " + stringOrNumberParameter + ", stringParameter = " + stringParameter);
else
alert("Variant #1: stringParameter = " + stringOrNumberParameter);
}
}

我倒希望是。我也想要这个特性,但是 TypeScript 需要与没有重载方法的非类型化 JavaScript 进行互操作。也就是说。如果您的重载方法是从 JavaScript 调用的,那么它只能被分派到一个方法实现。

有几个相关的讨论,例如。

Https://typescript.codeplex.com/workitem/617

我仍然认为 TypeScript 应该生成所有的 if‘ ing 和切换,这样我们就不需要这样做了。

为什么不使用 可选属性定义接口作为函数参数. 。

对于这个问题中的情况,使用一个定义了一些可选属性的内联接口只能直接使代码像下面这样:

class TestClass {


someMethod(arg: { stringParameter: string, numberParameter?: number }): void {
let numberParameterMsg = "Variant #1:";
if (arg.numberParameter) {
numberParameterMsg = `Variant #2: numberParameter = ${arg.numberParameter},`;
}
alert(`${numberParameterMsg} stringParameter = ${arg.stringParameter}`);
}
}


var testClass = new TestClass();
testClass.someMethod({ stringParameter: "string for v#1" });
testClass.someMethod({ numberParameter: 12345, stringParameter: "string for v#2" });

因为正如其他人在评论中提到的,TypeScript 中提供的重载只是一个函数的不同签名列表,而不支持其他静态语言那样的相应实现代码。因此,实现仍然只能在一个函数体中完成,这使得类型脚本中函数重载的使用不如支持真正重载特性的语言那么舒适。

然而,仍然有许多新的和方便的东西提供了类型脚本,这是不可用的遗留编程语言,其中可选的属性支持在一个匿名接口是这样一种方法,以满足舒适区从遗留函数重载,我认为。

如果方法重载有很多变体,那么另一种方法就是创建一个包含所有参数的类。因此,只能按任意顺序传递所需的参数。

class SomeMethodConfig {
stringParameter: string;
numberParameter: number;


/**
*
*/
constructor(stringParameter: string = '012', numberParameter?: number) { // different ways to make a param optional
this.numberParameter = 456; // you can put a default value here
this.stringParameter = stringParameter; // to pass a value throw the constructor if necessary
}
}

你也可以创建一个带有默认值的构造函数,或者传递一些强制参数:

const config = new SomeMethodConfig('text');
config.numberParameter = 123; // initialize an optional parameter only if you want to do it
this.SomeMethod(config);