TypeScript中函数重载的简单解释

408 阅读2分钟

TypeScript中函数重载的简单解释

大多数函数都接受一组固定的参数。

但有些函数可以接受可变数量的参数,不同类型的参数,甚至可以根据你调用函数的方式返回不同的类型。为了注释这样的函数,TypeScript提供了函数重载功能。

让我们来看看函数重载是如何工作的。

目录

1.函数签名

让我们考虑一个函数,它返回给某个人的欢迎信息。

ts

function greet(person: string): string {
  return `Hello, ${person}!`;
}

上面的函数接受1个类型为string 的参数:人的名字。

调用该函数是非常简单的。

ts

greet('World'); // 'Hello, World!'

如果你想让greet() 这个函数更加灵活呢?例如,让它另外接受一个要问候的人的列表。

这样一个函数将接受一个字符串或字符串数组作为参数,并返回一个字符串或字符串数组。

如何注释这样一个函数呢?有2种方法。

第一种方法很简单,就是通过更新参数和返回类型直接修改函数签名。

下面是更新参数和返回类型后greet() 的样子。

ts

function greet(person: string | string[]): string | string[] {
  if (typeof person === 'string') {
    return `Hello, ${person}!`;
  } else if (Array.isArray(person)) {
    return person.map(name => `Hello, ${name}!`);
  }
  throw new Error('Unable to greet');
}

现在你可以通过2种方式调用greet() :

ts

greet('World');          // 'Hello, World!'
greet(['Jane', 'Joe']); // ['Hello, Jane!', 'Hello, Joe!']

直接更新函数签名以支持多种调用方式是通常的、好的方法。

然而,在有些情况下,你可能想采取另一种方法,分别定义你的函数可以被调用的所有方式。这种方法被称为函数重载

2.函数重载

第二种方法是使用函数重载功能。当函数签名相对复杂且涉及多种类型时,我推荐使用这种方法。

将函数重载付诸实践需要定义所谓的重载签名和一个实现签名

重载签名定义了函数的参数和返回类型,并且没有主体。一个函数可以有多个重载签名:对应于你可以调用该函数的不同方式。

实现签名,在另一边,也有参数类型和返回类型,但也有一个实现函数的主体。只能有一个实现签名。

让我们转换一下函数greet() ,以使用函数重载。

ts

// Overload signatures
function greet(person: string): string;
function greet(persons: string[]): string[];
 
// Implementation signature
function greet(person: unknown): unknown {
  if (typeof person === 'string') {
    return `Hello, ${person}!`;
  } else if (Array.isArray(person)) {
    return person.map(name => `Hello, ${name}!`);
  }
  throw new Error('Unable to greet');
}

greet() 函数有两个重载签名和一个实现签名。

每个重载签名都描述了可以调用该函数的一种方式。在greet() 函数的例子中,你可以用两种方式调用它:用一个字符串参数,或用一个字符串数组参数。

实现签名function greet(person: unknown): unknown { ... } ,包含了该函数如何工作的适当逻辑。

现在,和以前一样,你可以用字符串或字符串数组类型的参数来调用greet()

greet('World');          // 'Hello, World!'
greet(['Jane', 'Joe']);  // ['Hello, Jane!', 'Hello, Joe!']

2.1 重载签名是可调用的

虽然实现签名实现了函数的行为,但是它不能直接调用。只有重载签名是可调用的。

ts

greet('World');         // Overload signature callable
greet(['Jane', 'Joe']); // Overload signature callable
 
const someValue: unknown = 'Unknown';
greet(someValue);       // Implementation signature NOT callable
No overload matches this call.
  Overload 1 of 2, '(person: string): string', gave the following error.
    Argument of type 'unknown' is not assignable to parameter of type 'string'.
  Overload 2 of 2, '(persons: string[]): string[]', gave the following error.
    Argument of type 'unknown' is not assignable to parameter of type 'string[]'.

在上面的例子中,你不能用unknown (greet(someValue))类型的参数调用greet() 函数,即使实现签名接受unknown 参数。

2.2 实现签名必须是通用的

请注意,实现签名的类型应该是通用的,足以包括重载签名。

否则,TypeScript不会接受重载签名,因为它是不兼容的。

例如,如果你把实现签名的返回类型从unknown 修改为string

ts

// Overload signatures
function greet(person: string): string;
function greet(persons: string[]): string[];
This overload signature is not compatible with its implementation signature.
 
// Implementation signature
function greet(person: unknown): string {
  // ...
  throw new Error('Unable to greet');
}

那么重载签名function greet(persons: string[]): string[] 被标记为与function greet(person: unknown): string 不兼容。

string 因为实现签名的返回类型不够通用,与重载签名的 返回类型不兼容。string[]

3.方法重载

虽然在前面的例子中,函数重载被应用于一个普通函数。但是你也可以重载一个方法

在方法重载过程中,重载签名和实现签名现在都是类的一部分。

例如,让我们实现一个Greeter 类,有一个重载方法greet()

ts

class Greeter {
  message: string;
 
  constructor(message: string) {
    this.message = message;
  }
 
  // Overload signatures
  greet(person: string): string;
  greet(persons: string[]): string[];
 
  // Implementation signature
  greet(person: unknown): unknown {
    if (typeof person === 'string') {
      return `${this.message}, ${person}!`;
    } else if (Array.isArray(person)) {
      return person.map(name => `${this.message}, ${name}!`);
    }
    throw new Error('Unable to greet');
  }
}

Greeter 类包含greet() 重载方法:2个重载签名描述了如何调用该方法,而实现签名则包含了正确的实现。

由于方法重载,你可以用两种方式调用hi.greet() :使用一个字符串或使用一个字符串数组作为参数。

ts

const hi = new Greeter('Hi');
 
hi.greet('Angela');       // 'Hi, Angela!'
hi.greet(['Pam', 'Jim']); // ['Hi, Pam!', 'Hi, Jim!']

4.何时使用函数重载

当使用正确的方法时,函数重载可以大大增加可能以多种方式调用的函数的可用性。这在自动完成过程中特别有用:你可以在自动完成中作为单独的记录列出所有可能的重载。

然而,在有些情况下,我建议不要使用函数重载,而应坚持使用函数签名。

例如,不要对可选参数使用函数重载。

ts

// Not recommended
function myFunc(): string;
function myFunc(param1: string): string;
function myFunc(param1: string, param2: string): string;
function myFunc(...args: string[]): string {
  // implementation...
}

在函数签名中使用可选参数就足够了。

ts

// OK
function myFunc(param1?: string, param2: string): string {
  // implementation...
}

5.总结

TypeScript中的函数重载让你定义可以以多种方式调用的函数。

使用函数重载需要定义重载签名:一组具有参数和返回类型的函数,但没有主体。这些签名表明函数应该如何被调用。

此外,你必须写出函数的正确实现(实现签名):参数和返回类型,以及函数体。请注意,实现签名是不可调用的。

除了常规函数,类中的方法也可以重载。