От автора: большинство функций принимают фиксированный набор аргументов. Но некоторые функции могут принимать переменное количество аргументов, аргументы разных типов и даже могут возвращать разные типы в зависимости от того, как вы вызываете функцию. Для обеспечения такого поведения TypeScript предлагает перегрузку функций.
Посмотрим, как она работает.
1. Сигнатура функции
Рассмотрим функцию, которая возвращает сообщение Hello конкретному человеку:
function greet(person: string): string { return `Hello, ${person}!`; }
Вышеупомянутая функция принимает 1 аргумент типа string: имя человека. Вызвать функцию довольно просто:
greet('World'); // 'Hello, World!'
Что, если вы хотите сделать функцию greet() более гибкой? Например, сделайте так, чтобы она дополнительно принимала список лиц, которых нужно поприветствовать.
Такая функция могла бы принимать строку или массив строк в качестве аргумента, а также возвращать строку или массив строк.
Как реализовать такую функцию? Есть 2 подхода. Первый подход прост и включает в себя изменение сигнатуры функции напрямую путем обновления типа параметра и возвращаемого значения. Вот как выглядит greet() с обновлением типа параметра и возвращаемого значения:
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'); }
Теперь, вы можете вызывать greet() двумя способами:
greet('World'); // 'Hello, World!' greet(['Jane', 'Joe']); // ['Hello, Jane!', 'Hello, Joe!']
Обновление сигнатуры функции напрямую для поддержки нескольких способов вызова — это обычный и хороший подход. Однако бывают ситуации, когда вы можете захотеть воспользоваться альтернативным подходом и отдельно определить все способы вызова вашей функции. Такой подход называется перегрузкой функций.
2. Перегрузка функции
Второй подход — использовать перегрузку функции. Я рекомендую его, когда сигнатура функции относительно сложна и включает несколько типов.
Внедрение перегрузки на практике требует определения так называемых перегрузки сигнатуры и имплементации сигнатуры.
Перегрузка сигнатуры (оverload signatures) определяет параметры и типы возвращаемых данных функции и не имеет тела. Функция может иметь несколько перегрузок сигнатуры: в соответствии с различными способами вызова функции.
Имплементация сигнатуры (іmplementation signature), с другой стороны, также имеет типы параметров и тип возвращаемого значения, а также тело, реализующее функцию. Реализация сигнатуры может быть только одна. Преобразуем функцию greet(), чтобы использовать перегрузку функции:
// 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() имеет 2 перегрузки сигнатуры и одну имплементацию. Каждая перегрузка описывает один из способов вызова функции. В случае функции greet() вы можете вызвать ее двумя способами: со строковым аргументом или с массивом строковых аргументов.
Имплементация сигнатуры function greet(person: unknown): unknown { … } содержит логику работы функции. Теперь, как и раньше, вы можете вызывать greet() с аргументами типа строка или массив строк:
greet('World'); // 'Hello, World!' greet(['Jane', 'Joe']); // ['Hello, Jane!', 'Hello, Joe!']
2.1 Перегрузка сигнатуры является callable
Хотя имплементация сигнатуры реализует поведение функции, она не может быть вызвана напрямую. Вызывается только перегрузка сигнатуры.
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[]'.
В приведенном выше примере вы не можете вызывать функцию greet() с аргументом типа unknown ( greet(someValue)), даже если имплементация сигнатуры принимает аргумент unknown.
2.2 Имплементация сигнатуры должна быть общей
Имейте в виду, что тип имплементации сигнатуры должен быть достаточно общим, чтобы включать перегрузку сигнатуры. В противном случае TypeScript не примет перегрузку сигнатуры. Например, если вы измените тип возвращаемого значения имплементации сигнатуры с unknown на string:
// Overload signatures function greet(person: string): string; function greet(persons: string[]): string[];
Такая перегрузка сигнатуры несовместима с ее имплементацией.
// 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():
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() двумя способами: используя в качестве аргумента строку или массив строк.
const hi = new Greeter('Hi'); hi.greet('Angela'); // 'Hi, Angela!' hi.greet(['Pam', 'Jim']); // ['Hi, Pam!', 'Hi, Jim!']
4. Когда использовать перегрузку функций
Перегрузка функций при правильном использовании может значительно повысить удобство использования функций, которые можно вызывать разными способами. Это особенно полезно во время автозаполнения: вы перечисляете все возможные перегрузки в виде отдельных записей в автозаполнении.
Однако бывают ситуации, когда я бы рекомендовал не использовать перегрузку функции, а придерживаться сигнатуры функции. Например, не используйте перегрузку функции для необязательных параметров:
// Not recommended function myFunc(): string; function myFunc(param1: string): string; function myFunc(param1: string, param2: string): string; function myFunc(...args: string[]): string { // implementation... }
Достаточно использовать необязательные параметры в сигнатуре функции:
// OK function myFunc(param1?: string, param2: string): string { // implementation... }
5. Вывод
Перегрузка функций в TypeScript позволяет определять функции, которые можно вызывать разными способами.
Использование перегрузки функций требует определения сигнатур перегрузки: набора функций с параметрами и возвращаемыми типами, но без тела. Эти сигнатуры указывают, как должна быть вызвана функция.
Кроме того, вы должны написать правильную реализацию функции (имплементацию сигнатуры): параметры и возвращаемые типы, а также тело функции. Обратите внимание, что имплементация сигнатуры не является callable. Помимо обычных функций, методы в классах тоже могут быть перегруженными.
Автор: Dmitri Pavlutin
Источник: dmitripavlutin.com
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен