В этом уроке мы научимся использовать перегрузки функций.
Перегрузка функций — это возможность определить несколько версий одной функции, каждая из которых принимает свой набор параметров. Разберем на примере:
function concat(a: number, b: number): string;
function concat(a: string, b: string): string;
function concat(a: unknown, b: unknown): string {
if (typeof a === 'number' && typeof b === 'number') {
return `${a.toFixed()}${b.toFixed()}`;
}
return `${a}${b}`;
}
concat('one', 'two'); // onetwo
concat(3, 5.34); // 35
concat(1.33, 10); // 110
Здесь определяется одна функция concat()
. У нее две версии, которые выполняют конкатенацию, но делают это по-разному:
Реализация поведения для обеих версий делается в третьей функции с тем же именем. При этом описание параметров должно подходить под каждую версию функции. В примере выше типы параметров определены как unknown
. Это дает возможность вызывать функцию как со строками, так и с числами.
То, по какой ветке идти, определяется с помощью проверки типов. В примере выше достаточно проверить тип только первого параметра, так как второй в таком случае точно будет строкой. Это обеспечивает система типов и компилятор.
Для перегрузки необязательно использовать объявление функций. То же самое можно сделать с помощью стрелочной функции:
const concat: {
(a: number, b: number): string;
(a: string, b: string): string;
} = (a, b) => {
if (typeof a === 'number' && typeof b === 'number') {
return `${a.toFixed()}${b.toFixed()}`;
}
return `${a}${b}`;
}
// с использованием псевдонимов
type Overloaded = {
(a: number, b: number): string;
(a: string, b: string): string;
}
const concat: Overloaded = (a, b) => {...}
В этом случае не обязательно явно указывать типы параметров внутри функции.
Перегрузка функций не ограничивается двумя версиями. Их может быть сколько угодно. Главное, что в конце всегда описывается функция, которая является общей по параметрам для всех вариантов и внутри которой описывается вся логика для каждого варианта.
Опишем функцию add()
, которая может принимать на вход два или три числа и возвращает их сумму:
function add(a: number, b: number, c: number): number;
function add(a: number, b: number): number;
function add(a: string, b: string): string;
// Сигнатура подходит под все примеры выше
function add(a: unknown, b: unknown, c?: number): unknown {
// тут вся логика
if (c === undefined) {
// ...
}
}
В статических языках перегрузка функций используется достаточно часто, но в большинстве из них она устроена не как в TypeScript. В этих языках создается несколько разных функций, которые с точки зрения программиста имеют одно имя. Поэтому там не нужна общая функция. Логика каждого варианта описывается внутри. Это избавляет код от необходимости реализовывать условную логику.
// Пример на Kotlin
fun main() {
fun newYearCongratulate (name:String):String {
return "Hi ${name}! Happy New Year!"
}
fun newYearCongratulate (year: Number, name:String):String {
return "Hi ${name}! Happy New Year ${year}!"
}
println(newYearCongratulate("John")) // Hi John! Happy New Year!
println(newYearCongratulate(2023, "Elon")) // Hi Elon! Happy New Year 2023!
}
Зачем в TypeScript такая реализация и какие проблемы она решает? Это как и многое другое в TypeScript — попытка учесть все варианты написания кода на JavaScript и покрыть их типами для написания типобезопасного кода.
В JavaScript нередко создают функции, которые принимают на вход разные типы данных в разных вариациях. Перегрузка функций позволяет описать подобные функции в TypeScript, иначе пришлось бы использовать any
и следить за типами самостоятельно.
Технически после транспиляции в JavaScript остается одна функция — та, что содержит тело.
Перегрузка функций в TypeScript — это механизм, который стоит использовать, когда нет другого выбора. В большинстве случаев вместо перегрузки используются объединения или дженерики, о которых мы поговорим позже.
Перегрузка нужна, когда между параметрами есть зависимость, например, если оба параметра — строки, либо оба параметра — числа.
Реализуйте функцию newYearCongratulate()
, которая аналогична примеру на Kotlin из теории:
newYearCongratulate('John'); // Hi John! Happy New Year!
newYearCongratulate(2023, 'Mila'); // Hi Mila! Happy New Year 2023!
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1import sayHello from './index';
2
3test('function', () => {
4 expect(sayHello('John')).toBe('Hi John! Happy New Year!');
5 expect(sayHello(2023, 'Mila')).toBe('Hi Mila! Happy New Year 2023!');
6});
7
Решение учителя откроется через: