Короткий ответ: функцию как аргумент в TypeScript типизируют через сигнатуру callback: какие параметры она принимает и что возвращает. Например, (value: number) => string означает: функция принимает число и возвращает строку
Простой пример:
function formatPrice(value: number, formatter: (value: number) => string): string {
return formatter(value);
}
const result = formatPrice(1000, (value) => `${value} руб.`);
TypeScript понимает, что value внутри callback — число, а результат всей функции — строка
Вынести тип callback
Если callback используется несколько раз, лучше вынести тип:
type PriceFormatter = (value: number) => string;
function formatPrice(value: number, formatter: PriceFormatter): string {
return formatter(value);
}
Так код читается легче, особенно если у функции несколько параметров
Callback без возвращаемого значения
Если функция ничего не возвращает:
type Logger = (message: string) => void;
function runTask(logger: Logger) {
logger("Задача запущена");
}
void означает, что вызывающий код не должен рассчитывать на результат
Несколько параметров
type CompareFn<T> = (a: T, b: T) => number;
function sortItems<T>(items: T[], compare: CompareFn<T>): T[] {
return [...items].sort(compare);
}
Использование:
const numbers = sortItems([3, 1, 2], (a, b) => a - b);
TypeScript выводит, что a и b — числа
Функция как параметр в объекте
Часто callback передают в props:
type ButtonProps = {
label: string;
onClick: () => void;
};
function Button(props: ButtonProps) {
props.onClick();
}
Для callback с данными:
type User = {
id: number;
name: string;
};
type UserListProps = {
users: User[];
onSelect: (user: User) => void;
};
Так дочерний компонент может передать выбранного пользователя наверх, а TypeScript проверит форму объекта
Async callback
Если callback асинхронный:
type SaveUser = (name: string) => Promise<void>;
async function submit(name: string, saveUser: SaveUser) {
await saveUser(name);
}
Не пишите просто Function. Такой тип не говорит, какие параметры нужны и что вернется
Generic callback
Универсальный обработчик:
function mapItems<T, R>(items: T[], mapper: (item: T) => R): R[] {
return items.map(mapper);
}
const lengths = mapItems(["one", "three"], (word) => word.length);
Здесь T — тип элемента входного массива, R — тип результата callback. TypeScript выводит lengths как number[]
Частые ошибки
Первая ошибка — писать callback: Function. Это почти не дает проверки
Вторая ошибка — ставить any в параметрах callback
Третья ошибка — забывать возвращаемый тип, когда результат важен
Четвертая ошибка — путать функцию и результат вызова:
runTask(logger());
Если функция ожидает callback, нужно передать функцию:
runTask(logger);
Пятая ошибка — делать тип callback слишком широким. Если нужен User, не принимайте object
Когда callback лучше не передавать
Функция как аргумент полезна, когда поведение действительно должно быть настраиваемым: форматирование, фильтрация, обработка события, логирование. Но если callback просто скрывает одну строку бизнес-логики, код может стать менее понятным
Плохой признак:
function processUser(user: User, callback: (user: User) => void) {
callback(user);
}
Если всегда вызывается одна и та же операция, проще вызвать ее напрямую. Callback нужен там, где вызывающий код должен выбрать действие
Также не смешивайте callback с побочными эффектами без названия. onSave, onSelect, formatLabel, predicate читаются лучше, чем абстрактный fn
Самопроверка
Напишите функцию filterItems<T>(items: T[], predicate: (item: T) => boolean): T[]. Вызовите ее для массива чисел и массива пользователей. Если TypeScript правильно понимает тип item в обоих случаях, callback типизирован верно
Что почитать дальше по TypeScript
Если нужен общий маршрут по теме, откройте рубрику TypeScript. Для соседних задач пригодятся эти разборы:
- TypeScript Map: как типизировать ключи и значения
- Как типизировать массив объектов в TypeScript
- Как типизировать объект в TypeScript: практический разбор
- 10 вопросов по TypeScript и ответы на них