Типы функций и перегрузки

Функция как значение: описываем её тип целиком и знакомимся с перегрузками.

Тип функции описывает её сигнатуру — типы параметров и возврата — в форме (a: number) => string.

Функция — это тоже значение

В JavaScript функции передают в переменные и аргументы. Чтобы типизировать такую переменную, нужен тип функции. Он записывается стрелкой:

let formatter: (value: number) => string;

formatter = (n) => n.toFixed(2);  // ок: подходит по сигнатуре
formatter = (n) => n;             // Ошибка: возвращает number, а нужен string

Тип (value: number) => string читается как «функция, принимающая число и возвращающая строку». В стрелке параметру дают любое имя — важны только типы.

Зачем это: типизация колбэков

Главная польза — описание колбэков. Когда функция принимает другую функцию, её тип задаёт контракт для колбэка:

function processItems(
  items: number[],
  callback: (item: number) => void
): void {
  for (const item of items) {
    callback(item);
  }
}

processItems([1, 2, 3], (n) => console.log(n * 10));

TypeScript проверит, что переданный колбэк соответствует сигнатуре: принимает число и не обязан ничего возвращать. Если колбэк ждёт строку — будет ошибка ещё до запуска.

Проверяемый пример

function applyTwice(fn, value) {
  return fn(fn(value));
}

const addExclaim = (s) => s + "!";
console.log(applyTwice(addExclaim, "Ура"));

const square = (x) => x * x;
console.log(applyTwice(square, 3));

Вывод:

Ура!!
81

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

Иногда функция ведёт себя по-разному в зависимости от типов аргументов. Перегрузка позволяет описать несколько вариантов сигнатуры для одной функции. Сначала идут объявления-сигнатуры, затем одна реализация:

// Сигнатуры-перегрузки:
function double(x: number): number;
function double(x: string): string;
// Одна реализация, покрывающая все варианты:
function double(x: number | string): number | string {
  if (typeof x === "number") {
    return x * 2;
  }
  return x + x;
}

const a = double(5);      // тип результата: number
const b = double("ля");   // тип результата: string

Без перегрузок результат имел бы тип number | string в обоих случаях, и пришлось бы сужать его вручную. Перегрузки дают точный тип для каждого варианта вызова: для числа — number, для строки — string.

Когда нужны перегрузки

Перегрузки — инструмент нечастый. Чаще достаточно union-типа или дженериков (о них в последнем разделе). Перегрузки оправданы, когда связь между типом аргумента и типом результата нельзя выразить иначе — как в примере выше.

Итог

  • Тип функции (a: T) => R описывает сигнатуру и нужен для переменных и колбэков.
  • Типизированный колбэк задаёт контракт: компилятор проверит переданную функцию.
  • Перегрузки описывают несколько сигнатур одной функции и дают точный тип результата для каждого варианта вызова.
Проверьте себя
1. Как читается тип (value: number) => string?
AОбъект с полями value и string
BФункция, принимающая число и возвращающая строку
CМассив из чисел и строк
DПеременная типа number или string
2. Зачем типизировать колбэк через тип функции?
AЧтобы колбэк выполнялся быстрее
BЧтобы задать контракт и проверить, что переданная функция подходит по сигнатуре
CЧтобы запретить передавать функции
DЧтобы колбэк всегда возвращал строку
3. Что дают перегрузки функции в примере с double?
AНесколько реализаций одной функции в рантайме
BТочный тип результата для каждого варианта вызова (number для числа, string для строки)
CВозможность вызвать функцию без аргументов
DУскорение функции
Поддержать проект