Перейти к содержимому
Теория · Функции · урок 9 из 10

Перегрузки функций

Бывают автоматы с двумя табличками. На одной написано: «Монеты — выдаёт чай». На другой: «Карта — выдаёт чай и чек». Механизм внутри один, но что вы получите, зависит от того, чем платите, и каждая табличка честно говорит это для своего случая.

Функции тоже бывает нужно сказать «что дали, того же вида и верну». Возьмём double: строку она удваивает склейкой ("ab" → "abab"), а число — умножением (21 → 42). С одной сигнатурой через объединение получается так:

TypeScript
function double(x: string | number): string | number {
    if (typeof x === "string") {
        return x + x;
    }
    return x * 2;
}

console.log(double(21).toFixed(1)); // ✗
TEXT
error TS2339: Property 'toFixed' does not exist on type 'string | number'.

Сигнатура обещает: «на входе строка или число, на выходе строка или число». Связи между входом и выходом в ней нет. Поэтому у double(21) тип string | number, а у такого значения можно вызвать только то, что есть у обоих типов. При запуске double(21) вернул бы 42, но tsc по сигнатуре этого не знает.

Сигнатуры перегрузки

Перегрузки функции — несколько сигнатур без тела, которые стоят перед одной функцией с телом. Каждая перегрузка — отдельная табличка: какой аргумент берёт и что за него возвращает.

TypeScript
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

Разберём по строкам:

  1. function double(x: string): string; — первая перегрузка: строка на входе, строка на выходе. Тела нет, в конце точка с запятой.
  2. function double(x: number): number; — вторая перегрузка, для чисел.
  3. Третье объявление — реализация, единственная функция с телом. Её параметр принимает всё, что разрешают перегрузки, а тело само проверяет через typeof, что пришло.
  4. Теперь у double(21) тип number, вызов .toFixed(1) проходит, и программа печатает abab 42.0.

Для каждого вызова tsc перебирает перегрузки сверху вниз и берёт первую, к которой подходят аргументы. Её тип результата и получает вызов.

Что видит вызывающий код

Снаружи видны только перегрузки, а сигнатура реализации скрыта. В сообщениях tsc тип этой функции выглядит так: { (x: string): string; (x: number): number; }. Две таблички есть, а string | number из реализации нет.

Отсюда правило: вызов должен подойти хотя бы к одной перегрузке целиком. То, что реализация приняла бы аргумент, не помогает: её сигнатуру tsc для вызовов не использует. Если не подошла ни одна перегрузка, tsc сообщает об ошибке TS2769.

Когда хватит объединения

Этот вопрос часто задают на собеседовании: «Перегрузки или параметр-объединение?» Перегрузки нужны, когда тип результата зависит от типа аргумента, как у double. Если результат один и тот же, хватит одной сигнатуры:

TypeScript
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[] не подошло бы ни к одной из них.

Частая ошибка

Функцию с перегрузками вызвали со значением типа-объединения:

TypeScript
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); // ✗
}
TEXT
error TS2769: No overload matches this call.

Сообщение значит: «ни одна перегрузка не подходит к этому вызову». v может быть строкой, поэтому не подходит вторая перегрузка, и может быть числом, поэтому не подходит первая. Здесь аналогия с автоматом заканчивается. Покупатель всегда держит в руке что-то одно, а значение v для tsc — «то ли монета, то ли карта», и ни одна табличка такого не обещает. Исправить можно двумя способами: проверить v через typeof до вызова или добавить третью перегрузку function double(x: string | number): string | number;.

Главное

  • Перегрузки — несколько сигнатур без тела перед одной реализацией; каждая связывает тип аргумента с типом результата.
  • Вызывающий код видит только перегрузки: сигнатура реализации снаружи не видна.
  • Вызов, который не подходит ни к одной перегрузке целиком, — ошибка TS2769, в том числе вызов со значением типа string | number.
  • Если тип результата не зависит от типа аргумента, вместо перегрузок пишут один параметр-объединение.