Перейти к основному содержимому

Функции в TypeScript

Разработчику Архитектору

Дальше: Дженерики · Асинхронность · Объекты и классы · Справочник — функции

Учебное раскрытие Справочник — функции: сигнатуры, overloads, this, generic-функции.


В 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
}

Для публичного 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.

Тип функции (сигнатура)

Функция — тоже тип. Его выносят в 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:

type User = { id: string; name: string };

type FetchCallbacks = {
onStart?: () => void;
onSuccess: (user: User) => void;
onError: (error: unknown) => void;
};

async function fetchUser(id: string, callbacks: FetchCallbacks): Promise<void> {
callbacks.onStart?.();
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = (await res.json()) as User;
callbacks.onSuccess(user);
} catch (error: unknown) {
callbacks.onError(error);
}
}

Разбор:


Стрелочные функции и this

В JavaScript this зависит от способа вызова. Стрелочные функции не имеют своего this — берут из внешней области.

class Counter {
count = 0;

// Обычный метод — свой this
increment() {
this.count += 1;
}

// Стрелка как поле — this зафиксирован на экземпляре
incrementBound = () => {
this.count += 1;
};
}

const c = new Counter();
const fn = c.increment;
// fn(); // в strict JS/TS — this может быть undefined

const bound = c.incrementBound;
bound(); // OK: count увеличится

Для DOM в TS типизируют this явно, если нужен обычный метод:

button.addEventListener("click", function (this: HTMLButtonElement, event: MouseEvent) {
this.disabled = true;
});

Подробнее про классы — 18.md; события — 20.md.


Перегрузки (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

Сначала тип события, затем обработчик:

type InputEvent = {
type: "input";
value: string;
};

type ClickEvent = {
type: "click";
x: number;
y: number;
};

type AppEvent = InputEvent | ClickEvent;

function handleEvent(event: AppEvent): void {
switch (event.type) {
case "input":
console.log(event.value);
return;
case "click":
console.log(event.x, event.y);
return;
}
}

Компилятор проверяет, что в каждой ветке доступны нужные поля — см. операторы.


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>

Практика

  1. Опишите type OnSave = (data: FormData) => void и функцию submit(form: FormData, hooks: { onSave: OnSave }).
  2. Напишите перегрузку parse(s: string): number и parse(n: number): string с одной реализацией.
  3. Реализуйте map<T, U>(items: T[], fn: (item: T) => U): U[] и используйте с разными типами.
  4. В классе замените метод, передаваемый в setTimeout, на стрелочное поле — убедитесь, что this корректен.
  5. Добавьте handleEvent с discriminated union из трёх вариантов и exhaustive default с never.

Смежные статьи