Как типизировать функцию обратного вызова (callback)?

Функции обратного вызова (callback) активно применяются в TypeScript при работе с асинхронными операциями, обработчиками событий и функциональными методами массивов. Правильная типизация callback помогает избежать ошибок при передаче аргументов и гарантирует, что разработчик использует функцию именно так, как задумано.

Базовый принцип типизации callback

Callback — это просто функция, передаваемая в другую функцию как аргумент. Чтобы её типизировать, необходимо указать типы параметров и возвращаемого значения.

Пример:

function processData(data: string, callback: (result: number) => void): void {
const processed = data.length;
callback(processed);
}

Здесь callback принимает число и ничего не возвращает (void).

Использование:

processData("hello", (len) => {
console.log(\`Длина строки: ${len}\`);
});

Использование алиасов типов и интерфейсов

Чтобы не дублировать сигнатуру callback в разных местах, удобно вынести её в отдельный тип или интерфейс.

Через type:

type Callback = (value: number, index: number) => void;
function iterateArray(arr: number\[\], cb: Callback): void {
arr.forEach(cb);
}

Через interface:

interface Callback {
(value: string): boolean;
}
function filterStrings(arr: string\[\], cb: Callback): string\[\] {
return arr.filter(cb);
}

Оба подхода упрощают читаемость и повышают переиспользуемость кода.

Опциональные параметры в callback

Иногда функция обратного вызова может принимать разное количество параметров. В таких случаях можно указать опциональные аргументы:

type Logger = (message: string, level?: string) => void;
function log(cb: Logger) {
cb("Система запущена");
cb("Ошибка соединения", "error");
}

Callback с возвращаемым значением

Важно помнить, что callback может возвращать значение. Например, метод map ожидает, что callback вернёт преобразованное значение.

function transformArray(arr: number\[\], cb: (el: number) => string): string\[\] {
return arr.map(cb);
}
const result = transformArray(\[1, 2, 3\], (num) => \`Число: ${num}\`);

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

Универсальные (generic) callback

В случаях, когда callback должен работать с разными типами данных, применяются дженерики.

function applyOperation<T, R>(value: T, cb: (input: T) => R): R {
return cb(value);
}
const numToStr = applyOperation(42, (n) => n.toString()); // string
const strToLen = applyOperation("hello", (s) => s.length); // number

Здесь callback получает значение одного типа (T) и возвращает значение другого (R). Такой подход делает функцию максимально гибкой.

Контекст (this) в callback

Иногда необходимо указать тип контекста this внутри callback. TypeScript позволяет это сделать явно.

interface User {
name: string;
}
function runWithUser(cb: (this: User) => void) {
cb.call({ name: "Иван" });
}
runWithUser(function () {
console.log(this.name); // "Иван"
});

Здесь указывается, что внутри callback this будет объектом типа User.

Таким образом, типизация callback в TypeScript может быть простой или довольно сложной, в зависимости от сценария. Она обеспечивает контроль над входными и выходными значениями функции, помогает создавать безопасный код и уменьшает количество ошибок при интеграции разных частей системы.