🎯 Зачем спрашивают
- Проверка знаний о типизации TS
У кандидата хотят выяснить, понимает ли он, что TS даёт надстройку над JS — в частности возможность описывать разные сигнатуры для одной функции.
- Умение писать удобный API
Перегрузки часто используют в библиотеках и SDK (например,
fetch, querySelector, Array.from). Интервьюер смотрит, способен ли разработчик делать интерфейсы «с разными входами, но предсказуемым выходом».- Разделение объявления и реализации
Проверяют, знает ли кандидат, что в TS можно объявить несколько сигнатур, но реализация остаётся одна — и её нужно корректно типизировать и защитить проверками.
- Глубина владения инструментом
Если кандидат путает перегрузку с обычными опциональными параметрами или union-типа аргументов — это сигнал, что он работает скорее «по-верхам».
📝 Ответ
Коротко:
Перегрузка функций — механизм, позволяющей функции принимать разные наборы аргументов и возвращать разные типы в зависимости от того, как её вызывают.
// Объявляем перегрузки function add(a: number, b: number): number; function add(a: string, b: string): string; // Реализация (обязательна одна, должна покрывать все кейсы) function add(a: number | string, b: number | string): number | string { if (typeof a === "number" && typeof b === "number") { return a + b; } if (typeof a === "string" && typeof b === "string") { return a + b; } throw new Error("Неверные аргументы"); } // Использование let x = add(2, 3); // number let y = add("a", "b"); // string // @ts-expect-error add("a", 1);// компилятор не пропустит
Подробнее:
В TS реализуется только одно тело функции, а перегружаются именно её сигнатуры
В статических языках перегрузка функций используется достаточно часто, но в большинстве из них она устроена не как в TypeScript. В этих языках создается несколько разных функций, которые имеют одно имя. Поэтому там не нужна общая функция. Логика каждого варианта описывается внутри. Это избавляет код от необходимости реализовывать условную логику.
С#
class Printer { public void Print(int n) { Console.WriteLine(n); } public void Print(string s) { Console.WriteLine(s); } } var p = new Printer(); p.Print(10); // вызывает версию с int p.Print("Hi"); // вызывает версию со string
Kotli
fun greet(name: String) = "Hello, $name" fun greet(count: Int) = "Hello x$count" greet("Ada") greet(3)
Подбор перегрузки идёт сверху вниз
TypeScript проверяет перегрузки в порядке их объявления и берёт первую подходящую. Поэтому если у тебя сначала стоит «слишком общая сигнатура», она перехватит вызов раньше, чем дойдёт до более специфичной.
// Общая сигнатура стоит первой function combine(a: string | number, b: string | number): string | number; function combine(a: string, b: string): string; function combine(a: any, b: any): any { return a + b; } // ❌ Тип r: string | number // хотя у нас есть перегрузка string+string → string const r = combine("a", "b");
Это происходит, потому что TS проверяет первую перегрузку:
(a: string | number, b: string | number)
Подходит? Да. Берёт её.До
(a: string, b: string) он даже не доходит.⚖️ Компромиссы
✅ Плюсы | ❌ Минусы |
Чёткие контракты API: IDE подсказки, автодокументация, точные return-type по кейсам. | Сложнее поддерживать: легко запутаться в порядке перегрузок и совместимости. |
Удобно, когда от видов аргументов реально меняется тип результата. | Переусложняет API там, где хватит union/дженерика. |
ㅤ | Иногда вызывает неоднозначности (нужно упорядочивать от частного к общему). |
🔎 Встречные вопросы
- Когда выбрать перегрузки, а когда
union/дженерик?
- Почему реализация должна иметь наиболее общий тип аргументов?
- Как перегрузить стрелочную функцию? (через тип с несколькими call-сигнатурами)
- Что будет, если порядок перегрузок поставить «общая выше частной»?
🚩 Красные флаги
- В рантайме существует несколько функций (нет, реализация одна).
- Достаточно написать самую общую сигнатуру реализации, перегрузки не нужны (потеряешь точный return-type).
- Использование перегрузки без type guards в реализации → потенциальные рантайм-ошибки.
🛠 Практика
Перегрузите функцию
// Типизировать функцию test любым способом function test() { } test('test string'); // ✅ test(2, 3); // ✅ test('str', 3) // ❌ test(3, 'str') // ❌ test('str', 'str') // ❌ test(3) // ❌
Перегрузите стрелочную функцию
const test = (a, b) => { }; test('test string'); // ✅ test(2, 3); // ✅ test('str', 3) // ❌ test(3, 'str') // ❌ test('str', 'str') // ❌ test(3) // ❌