Перегрузки функций
Бывают автоматы с двумя табличками. На одной написано: «Монеты — выдаёт чай». На другой: «Карта — выдаёт чай и чек». Механизм внутри один, но что вы получите, зависит от того, чем платите, и каждая табличка честно говорит это для своего случая.
Функции тоже бывает нужно сказать «что дали, того же вида и верну». Возьмём double: строку она удваивает склейкой ("ab" → "abab"), а число — умножением (21 → 42). С одной сигнатурой через объединение получается так:
function double(x: string | number): string | number {
if (typeof x === "string") {
return x + x;
}
return x * 2;
}
console.log(double(21).toFixed(1)); // ✗error TS2339: Property 'toFixed' does not exist on type 'string | number'.Сигнатура обещает: «на входе строка или число, на выходе строка или число». Связи между входом и выходом в ней нет. Поэтому у double(21) тип string | number, а у такого значения можно вызвать только то, что есть у обоих типов. При запуске double(21) вернул бы 42, но tsc по сигнатуре этого не знает.
Сигнатуры перегрузки
Перегрузки функции — несколько сигнатур без тела, которые стоят перед одной функцией с телом. Каждая перегрузка — отдельная табличка: какой аргумент берёт и что за него возвращает.
function double(x: string): string;
function double(x: number): number;
function double(x: string | number): string | number {
if (typeof x === "string") {
return x + x;
}
return x * 2;
}
console.log(double("ab"), double(21).toFixed(1)); // abab 42.0Разберём по строкам:
function double(x: string): string;— первая перегрузка: строка на входе, строка на выходе. Тела нет, в конце точка с запятой.function double(x: number): number;— вторая перегрузка, для чисел.- Третье объявление — реализация, единственная функция с телом. Её параметр принимает всё, что разрешают перегрузки, а тело само проверяет через
typeof, что пришло. - Теперь у
double(21)типnumber, вызов.toFixed(1)проходит, и программа печатаетabab 42.0.
Для каждого вызова tsc перебирает перегрузки сверху вниз и берёт первую, к которой подходят аргументы. Её тип результата и получает вызов.
Что видит вызывающий код
Снаружи видны только перегрузки, а сигнатура реализации скрыта. В сообщениях tsc тип этой функции выглядит так: { (x: string): string; (x: number): number; }. Две таблички есть, а string | number из реализации нет.
Отсюда правило: вызов должен подойти хотя бы к одной перегрузке целиком. То, что реализация приняла бы аргумент, не помогает: её сигнатуру tsc для вызовов не использует. Если не подошла ни одна перегрузка, tsc сообщает об ошибке TS2769.
Когда хватит объединения
Этот вопрос часто задают на собеседовании: «Перегрузки или параметр-объединение?» Перегрузки нужны, когда тип результата зависит от типа аргумента, как у double. Если результат один и тот же, хватит одной сигнатуры:
function size(x: string | number[]): number {
return x.length;
}
console.log(size("abc"), size([1, 2])); // 3 2У строки и у массива есть length, и в обоих случаях результат — число. Перегрузки size(x: string): number и size(x: number[]): number ничего бы не добавили. Наоборот, они бы помешали: значение типа string | number[] не подошло бы ни к одной из них.
Частая ошибка
Функцию с перегрузками вызвали со значением типа-объединения:
function double(x: string): string;
function double(x: number): number;
function double(x: string | number): string | number {
if (typeof x === "string") {
return x + x;
}
return x * 2;
}
function twice(v: string | number) {
return double(v); // ✗
}error TS2769: No overload matches this call.Сообщение значит: «ни одна перегрузка не подходит к этому вызову». v может быть строкой, поэтому не подходит вторая перегрузка, и может быть числом, поэтому не подходит первая. Здесь аналогия с автоматом заканчивается. Покупатель всегда держит в руке что-то одно, а значение v для tsc — «то ли монета, то ли карта», и ни одна табличка такого не обещает. Исправить можно двумя способами: проверить v через typeof до вызова или добавить третью перегрузку function double(x: string | number): string | number;.
Главное
- Перегрузки — несколько сигнатур без тела перед одной реализацией; каждая связывает тип аргумента с типом результата.
- Вызывающий код видит только перегрузки: сигнатура реализации снаружи не видна.
- Вызов, который не подходит ни к одной перегрузке целиком, — ошибка
TS2769, в том числе вызов со значением типаstring | number. - Если тип результата не зависит от типа аргумента, вместо перегрузок пишут один параметр-объединение.