Функции в TypeScript
Дальше: Дженерики · Асинхронность · Объекты и классы · Справочник — функции
В JavaScript функция — значение первого класса: её передают в аргументах, возвращают из других функций, хранят в переменных. TypeScript добавляет контракт сигнатуры — какие аргументы допустимы, что возвращается, как типизировать callback и перегрузки. Удобный порядок — типы и типизация, эта статья, дженерики, асинхронность. Таблицы сигнатур — Справочник — функции.
База по JS: функции и объекты. Общая теория — блоки кода.
Типизация функций и сигнатуры
| Без типов (JS) | С TypeScript |
|---|---|
| Неверный аргумент всплывает в runtime | Ошибка в IDE и при tsc |
| Callback с лишним полем не заметен | Сигнатура onSuccess(data: User) |
| Рефакторинг параметра ломает вызовы молча | Компилятор показывает все места |
Проверка идёт на этапе компиляции — в .js аннотации исчезают, runtime остаётся JavaScript.
Объявление функций
Обычная функция
function sum(a: number, b: number): number {
return a + b;
}
Разбор:
- После имени параметра — тип:
a: number. - После
)— тип возврата:: number. return "text"вызовет ошибку компиляции.
Стрелочная функция
const multiply = (x: number, y: number): number => x * y;
const greet = (name: string): string => {
return `Привет, ${name}`;
};
Разбор:
- С телом
{ }нужен явныйreturn(кромеvoid). - Выражение после
=>— неявныйreturn(как в JS).
Вывод типа возврата
Если тип возврата не указан, компилятор выводит его из return:
function double(n: number) {
return n * 2; // выведено: number
}
Явный возвращаемый тип
Возвращаемый тип — часть публичного контракта функции. Явная запись : T после параметров фиксирует ожидание вызывающего:
function parsePort(raw: string): number | null {
const n = Number(raw);
return Number.isFinite(n) ? n : null;
}
Когда указывать : ReturnType явно | Когда достаточно вывода |
|---|---|
| экспортируемые функции, библиотеки | приватные хелперы внутри модуля |
несколько return с разными ветками | однострочная стрелка с очевидным типом |
| нужно запретить лишние поля в возврате | прототип, тип меняется вместе с телом |
async — всегда Promise<T> | — |
// Ошибка: забыли обработать null — явный возврат помогает при рефакторинге
function badPort(raw: string): number {
const n = Number(raw);
return Number.isFinite(n) ? n : null; // Type 'null' is not assignable to type 'number'
}
Утилиты ReturnType<T>, Awaited<T> извлекают возврат из типа функции — §утилиты для сигнатур.
Для публичного API (экспорт модуля) лучше указать возврат явно — так контракт останется стабильным при правках тела функции.
void, undefined и отсутствие return
function log(message: string): void {
console.log(message);
// return; — допустимо
// return undefined; — допустимо
// return 42; — ошибка
}
| Тип | Смысл |
|---|---|
void | Вызывающему не важен результат |
undefined | Должны вернуть именно undefined |
без return | Для void — OK; для строгого undefined — нужен return undefined |
Async-функции возвращают Promise<T> — асинхронность.
Параметры — обязательные, опциональные, по умолчанию
function greet(name: string, title?: string): string {
return title ? `${title} ${name}` : name;
}
function createId(prefix = "id"): string {
return `${prefix}-${Date.now()}`;
}
Разбор:
title?— аргумент можно не передавать; типstring | undefined.prefix = "id"— TypeScript выводит типstringиз значения по умолчанию.- Опциональные параметры должны идти после обязательных (как в JS).
Rest-параметры
function logAll(level: string, ...messages: string[]): void {
for (const m of messages) {
console.log(`[${level}]`, m);
}
}
logAll("info", "старт", "готово");
Тип rest — массив (string[]), без any[], если включён strict.
Деструктуризация параметров
type Point = { x: number; y: number };
function printPoint({ x, y }: Point): void {
console.log(x, y);
}
function connect({ host, port = 8080 }: { host: string; port?: number }): void {
console.log(`${host}:${port}`);
}
Разбор:
- Вся форма объекта — один аргумент с типом
Point. - Значения по умолчанию работают как в JS; тип
portвыводится какnumber.
Один объект вместо многих аргументов (RORO)
RORO (receive an object, return an object) — функция принимает один объект опций вместо длинного списка параметров. В TypeScript это даёт именованные поля, опциональность и устойчивость к изменению API.
// Сложно расширять: новый параметр ломает все вызовы
function createUserBad(name: string, email: string, role: string, sendEmail: boolean) {
// ...
}
type CreateUserOptions = {
name: string;
email: string;
role?: "admin" | "user";
sendWelcomeEmail?: boolean;
};
function createUser(options: CreateUserOptions) {
const { name, email, role = "user", sendWelcomeEmail = true } = options;
return { id: crypto.randomUUID(), name, email, role, sendWelcomeEmail };
}
createUser({ name: "Анна", email: "a@b.c" });
createUser({ name: "Борис", email: "b@c.d", role: "admin", sendWelcomeEmail: false });
| Критерий | Много аргументов | Объект опций |
|---|---|---|
| Читаемость вызова | позиция важна | имена полей |
| Опциональные параметры | только в конце списка | любое поле с ? |
| Добавление поля | часто ломает вызовы | обратно совместимо |
| Типизация | перегрузки | один type / interface |
Порог: три и больше параметра или два и больше опциональных — обычно пора переходить на объект. Альтернатива для перегрузки сигнатур — overloads.
Тип функции (сигнатура)
Функция — тоже тип. Его выносят в type или interface:
type BinaryOp = (x: number, y: number) => number;
const add: BinaryOp = (a, b) => a + b;
const sub: BinaryOp = (a, b) => a - b;
type UserHandler = (user: User) => void;
function loadUser(id: string, onSuccess: UserHandler, onError: (err: Error) => void) {
// ...
}
Разбор:
BinaryOpописывает любую функцию с двумяnumberи результатомnumber.- Имена параметров в типе (
x,y) не обязаны совпадать с реализацией (a,b).
Callable interface
interface Validator {
(value: string): boolean;
cache?: Map<string, boolean>;
}
const validateEmail: Validator = (value) => value.includes("@");
Подходит, когда функция — объект с дополнительными полями (кэш, метаданные).
Таблицы сигнатур — Справочник — функции и интерфейсы.
Callback и события
Типичный паттерн в UI и API:
Код ITЗагрузка примера кода…
Разбор:
onStart?— опциональный callback без аргументов.onErrorпринимаетunknownвместоany— см. Типы и типизация и Обработка ошибок.?.()безопасно вызывает отсутствующий callback.
Стрелочные функции и this
В JavaScript this зависит от способа вызова. Стрелочные функции не имеют своего this — берут из внешней области.
Код ITЗагрузка примера кода…
Для DOM в TS типизируют this явно, если нужен обычный метод:
button.addEventListener("click", function (this: HTMLButtonElement, event: MouseEvent) {
this.disabled = true;
});
Подробнее про классы — Объекты и классы в TypeScript; события — События и обработка событий в TypeScript.
Перегрузки (overloads)
Несколько сигнатур для одной реализации — удобный API для вызывающего:
function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
return String(value);
}
const a = format("42"); // string
const b = format(42); // string
Разбор:
- Строки
function format(...)без тела — объявления перегрузок. - Последняя сигнатура с телом должна принимать объединение всех вариантов.
- Вызывающий видит точный возврат по типу аргумента; реализация одна.
Перегрузка с разным числом аргументов
function createElement(tag: "img"): HTMLImageElement;
function createElement(tag: "div"): HTMLDivElement;
function createElement(tag: string): HTMLElement;
function createElement(tag: string): HTMLElement {
return document.createElement(tag);
}
Не злоупотребляйте перегрузками: часто проще один аргумент-объект или discriminated union — Типы и типизация.
Функции высшего порядка
Функция, принимающая или возвращающая другую функцию:
function filter<T>(items: T[], predicate: (item: T) => boolean): T[] {
const result: T[] = [];
for (const item of items) {
if (predicate(item)) result.push(item);
}
return result;
}
const evens = filter([1, 2, 3, 4], (n) => n % 2 === 0);
const long = filter(["a", "bb", "ccc"], (s) => s.length > 1);
Разбор:
<T>связывает тип элементов массива и тип вpredicate.- Без generic пришлось бы
any[]или дублировать код — см. дженерики.
Type-driven handler
Сначала тип события, затем обработчик:
Код ITЗагрузка примера кода…
Компилятор проверяет, что в каждой ветке доступны нужные поля — см. операторы.
Generic-функции
function identity<T>(value: T): T {
return value;
}
function first<T>(items: T[]): T | undefined {
return items[0];
}
Ограничения и keyof — в дженерики, контракты API — Паттерны.
Утилиты для сигнатур
Встроенные типы над типами функций:
type UserService = {
getUser(id: string): Promise<User>;
deleteUser(id: string): Promise<void>;
};
// Тип параметров функции
type DeleteParams = Parameters<UserService["deleteUser"]>; // [id: string]
// Тип возврата
type UserResult = Awaited<ReturnType<UserService["getUser"]>>; // User
Полный список — Справочник — утилитарные и расширенные типы.
noImplicitAny и параметры
С strict / noImplicitAny у каждого параметра должен быть известный тип (явно или через контекст):
// Ошибка без strict: (item) => ...
// С noImplicitAny: нужен тип item
items.forEach((item: string) => console.log(item));
// Или generic / тип массива задаёт item автоматически:
const names: string[] = ["a", "b"];
names.forEach((item) => console.log(item.toUpperCase()));
Частые ошибки
| Ошибка | Причина | Что делать |
|---|---|---|
Expected 2 arguments, but got 1 | Сигнатура не совпадает | Исправить вызов или сделать параметр опциональным |
Type '() => void' is not assignable... | Неверный callback | Сверить тип аргумента и Handler |
| Перегрузка не срабатывает | Тело не покрывает union | Реализация должна принимать все варианты |
this undefined в методе | Потерян контекст вызова | Стрелка, .bind, или поле-стрелка в классе |
Слишком широкий Function | Потеря типов | Конкретный (a: T) => R |
Async без Promise<T> | Забыли тип возврата | async function f(): Promise<User> |
Практика
- Опишите
type OnSave = (data: FormData) => voidи функциюsubmit(form: FormData, hooks: { onSave: OnSave }). - Напишите перегрузку
parse(s: string): numberиparse(n: number): stringс одной реализацией. - Реализуйте
map<T, U>(items: T[], fn: (item: T) => U): U[]и используйте с разными типами. - В классе замените метод, передаваемый в
setTimeout, на стрелочное поле — убедитесь, чтоthisкорректен. - Добавьте
handleEventс discriminated union из трёх вариантов и exhaustivedefaultсnever.
Смежные статьи
- Типы данных и типизация — union, narrowing,
unknown - Переменные и константы —
constдля функций - Дженерики —
<T>,extends,keyof - Асинхронность —
async/await,Promise<T> - Объекты и классы — методы, модификаторы
- Обработка ошибок —
catch (unknown) - Справочник раздела · Справочник — функции
- Обзор: Обзор TypeScript в JS