Avatar ·

Как правильно объявить тип для callback-функции в TypeScript?

Привет всем! Я недавно начал изучать TypeScript и столкнулся с проблемой при определении типов для callback-функций. У меня есть функция, которая принимает callback и вызывает его после какой-то обработки данных. Вот упрощённый пример: ```typescript function processData(data: string, callback) { const result = data.toUpperCase(); callback(result); } ``` TypeScript ругается на `callback` — говорит, что параметр неявно имеет тип `any`. Я понимаю, что нужно явно указать тип для callback, но не знаю, как это сделать правильно. Я пробовал разные варианты, например: ```typescript function processData(data: string, callback: Function) { // ... } ``` Но это работает, но не даёт контроля над тем, какие аргументы принимает callback и что он возвращает. Например, я хочу, чтобы callback принимал строку и ничего не возвращал (void). Как это правильно описать? Также интересует, как быть с опциональными callback-ами (когда их может не быть) и как передавать callback с несколькими параметрами. Буду благодарен за примеры и объяснения! <<>>

Основной способ: сигнатура вызова (call signature)

Лучший способ объявить тип callback-функции — использовать сигнатуру вызова (call signature) в TypeScript. Это позволяет явно указать, какие параметры принимает функция и что она возвращает.

В вашем случае, когда callback принимает один аргумент типа string и ничего не возвращает (void), правильный тип будет:

type CallbackType = (result: string) => void;

function processData(data: string, callback: CallbackType): void {
    const result = data.toUpperCase();
    callback(result);
}

// Использование:
processData("hello", (res) => {
    console.log(res); // HELLO
});

Тип void означает, что возвращаемое значение callback-а игнорируется. Вы можете передать функцию, которая возвращает число или что-то ещё — ошибки не будет, но результат не будет использован.

Объявление типа на месте (inline)

Можно не создавать отдельный тип, а написать сигнатуру прямо в параметре:

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

Callback с несколькими параметрами

Если callback принимает несколько аргументов, просто перечислите их в сигнатуре:

type UserCallback = (id: number, name: string, isActive: boolean) => void;

function fetchUser(id: number, callback: UserCallback): void {
    // Имитация запроса
    const user = { id, name: "Alice", isActive: true };
    callback(user.id, user.name, user.isActive);
}

fetchUser(1, (id, name, isActive) => {
    console.log(`${name} (ID: ${id}) активен: ${isActive}`);
});

Опциональный callback

Чтобы сделать callback необязательным, используйте оператор ? или объединение с undefined:

function processData(data: string, callback?: (result: string) => void): void {
    const result = data.toUpperCase();
    if (callback) {
        callback(result);
    }
}

// Можно вызвать без callback:
processData("hello");
// Или с callback:
processData("hello", (res) => console.log(res));

Callback, который может вернуть значение

Если callback должен вернуть какое-то значение (например, трансформировать данные), укажите тип возврата:

type Transformer = (input: string) => number;

function transformAndLog(data: string, transformer: Transformer): void {
    const result = transformer(data);
    console.log(`Результат трансформации: ${result}`);
}

transformAndLog("42", (str) => parseInt(str)); // Результат: 42

Использование обобщений (generics) для гибкости

Если callback должен работать с разными типами данных, можно сделать функцию обобщённой:

function processData<T>(data: T, callback: (value: T) => void): void {
    callback(data);
}

processData("строка", (val) => console.log(val.length)); // 6
processData(123, (val) => console.log(val.toFixed(2)));  // 123.00

Что НЕ стоит использовать

  • Function — слишком общий тип, не даёт контроля над параметрами
  • any — отключает проверку типов полностью
  • object — не подходит для функций

Используйте сигнатуру вызова — это самый безопасный и читаемый способ объявить callback в TypeScript.

<<>>

Альтернативный подход: интерфейсы с сигнатурой вызова

Вместо type можно использовать интерфейс с сигнатурой вызова. Это особенно удобно, если callback — это часть более сложного объекта или вы хотите добавить документацию:

interface StringCallback {
    (result: string): void;
    description?: string; // можно добавить свойства
}

function processData(data: string, callback: StringCallback): void {
    const result = data.toUpperCase();
    callback(result);
}

const myCallback: StringCallback = (res) => {
    console.log(res);
};
myCallback.description = "Просто логирует результат";

processData("hello", myCallback);

Callback с ошибками (Node.js стиль)

Если вы пишете код в стиле Node.js (первый аргумент — ошибка), используйте такой тип:

type NodeCallback<T> = (error: Error | null, result?: T) => void;

function readFile(path: string, callback: NodeCallback<string>): void {
    if (!path) {
        callback(new Error("Путь не указан"));
        return;
    }
    callback(null, "содержимое файла");
}

readFile("/test.txt", (err, data) => {
    if (err) {
        console.error(err.message);
        return;
    }
    console.log(data);
});

Этот подход широко используется в старых библиотеках и асинхронных операциях.