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 }даёт ошибку