TS функции и перегрузки

Функции в TypeScript типизируются через аннотации параметров и возвращаемого значения; перегрузки (overloads) позволяют определить несколько сигнатур для одной функции, точно описывая разные варианты вызова.

Зачем нужно

  • Точные сигнатуры функций устраняют класс ошибок «передал не тот тип» на этапе компиляции
  • Перегрузки позволяют описать полиморфное поведение — когда тип возвращаемого значения зависит от типа аргумента
  • Функциональные типы и generic-функции — основа переиспользуемых утилит

Где используется

  • Любая функция в TypeScript-коде
  • Перегрузки — API-методы с вариативными аргументами (querySelector, addEventListener)
  • Generic-функции — утилиты работы с массивами, промисами, объектами
  • Callback-и, event handler-ы, middleware

Основной контент

Аннотация функций

// Объявление функции
function greet(name: string): string {
  return `Hello, ${name}!`;
}

// Функциональное выражение
const add = function(a: number, b: number): number {
  return a + b;
};

// Стрелочная функция
const multiply = (a: number, b: number): number => a * b;

// Тип функции как переменная
type Transformer = (value: string) => string;
const toUpper: Transformer = s => s.toUpperCase();

Опциональные параметры и значения по умолчанию

function createUser(
  name: string,
  role: "admin" | "user" = "user", // значение по умолчанию
  email?: string,                    // опциональный
): User {
  return { id: Date.now(), name, role, email: email ?? "" };
}

createUser("Alice");                    // OK
createUser("Bob", "admin");             // OK
createUser("Carol", "user", "c@b.com"); // OK

Rest-параметры и spread

function sum(...nums: number): number {
  return nums.reduce((acc, n) => acc + n, 0);
}

sum(1, 2, 3, 4); // 10

// Типизированный spread
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

const result = merge({ id: 1 }, { name: "Alice" });
// result: { id: number } & { name: string }

Перегрузки (function overloads)

// Сигнатуры перегрузок — только для компилятора
function parse(input: string): number;
function parse(input: number): string;
// Реализация — должна покрывать все сигнатуры
function parse(input: string | number): number | string {
  if (typeof input === "string") return parseInt(input, 10);
  return input.toString();
}

const n = parse("42");  // n: number
const s = parse(42);    // s: string

Перегрузки с разным числом аргументов:

function createElement(tag: "div"): HTMLDivElement;
function createElement(tag: "span"): HTMLSpanElement;
function createElement(tag: "input"): HTMLInputElement;
function createElement(tag: string): HTMLElement {
  return document.createElement(tag);
}

const div = createElement("div");   // HTMLDivElement
const span = createElement("span"); // HTMLSpanElement

Generic-функции

// T — тип-параметр, выводится из аргумента
function identity<T>(value: T): T {
  return value;
}

identity(42);        // T = number
identity("hello");   // T = string

// Generic с ограничением (constraint)
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: "Alice" };
const name = getProperty(user, "name"); // string
getProperty(user, "age");               // Ошибка! "age" не в keyof user

// Generic-массив
function first<T>(arr: T): T | undefined {
  return arr[0];
}

Тип функции как интерфейс

// Callable interface
interface Formatter {
  (value: string, options?: { uppercase?: boolean }): string;
  version: string; // дополнительные свойства
}

const fmt: Formatter = (val, opts) => {
  return opts?.uppercase ? val.toUpperCase() : val;
};
fmt.version = "1.0";

// Тип функции высшего порядка
type Middleware<T> = (value: T, next: (value: T) => T) => T;

const logger: Middleware<string> = (val, next) => {
  console.log("before:", val);
  const result = next(val);
  console.log("after:", result);
  return result;
};

Частые ошибки

  • Реализация перегрузки не покрывает все сигнатуры — компилятор проверяет только внешние сигнатуры, а реализацию — частично
  • Слишком широкий тип в реализации перегрузкиstring | number | boolean в реализации, хотя сигнатуры покрывают только 2 варианта
  • Не указывать возвращаемый тип у сложных функций — TypeScript выведет, но явная аннотация защищает от случайного изменения
  • Использовать перегрузки вместо union-параметра — если поведение не меняется, достаточно a: string | number
  • Generic без ограничений к свойству — обращение к obj.length без T extends { length: number } даёт ошибку

Связанные темы

Ресурсы