Avatar ·

Строгая типизация функций как параметров в TypeScript: как объявить тип для callback?

Всем привет! Я недавно начал изучать TypeScript и столкнулся с проблемой при передаче функций в качестве аргументов. В JavaScript я часто делал так: ```typescript function processData(data, callback) { const result = data.map(item => item * 2); callback(result); } processData([1, 2, 3], function(result) { console.log(result); }); ``` Но в TypeScript мне нужно указать типы, и я не понимаю, как правильно объявить тип для параметра-функции. Я пробовал несколько вариантов, но они не работают: ```typescript // Вариант 1: просто указать any function processData(data: number[], callback: any) { ... } // Вариант 2: Function function processData(data: number[], callback: Function) { ... } ``` Первый вариант убивает всю суть TypeScript — теряется проверка типов. Второй тоже не даёт конкретики — я могу передать любую функцию, даже с неправильными параметрами. Как правильно объявить тип для callback, чтобы TypeScript проверял: 1. Сколько параметров принимает функция 2. Какого типа эти параметры 3. Что она возвращает Мне нужно что-то вроде: "эта функция должна принимать массив чисел и ничего не возвращать". Подскажите, как это сделать? Заранее спасибо! <<>>

Сигнатуры функций в TypeScript

Да, в TypeScript можно строго типизировать функции, передаваемые как параметры. Для этого используются сигнатуры функций (function type expressions). Они позволяют явно указать, какие аргументы принимает функция и что возвращает.

Базовый синтаксис

Сигнатура функции записывается в формате:

(параметры) => возвращаемый_тип

Вот как исправить ваш пример:

function processData(
    data: number[], 
    callback: (result: number[]) => void
): void {
    const result = data.map(item => item * 2);
    callback(result);
}

// Использование:
processData([1, 2, 3], (result) => {
    result.forEach(r => console.log(r));
});

Здесь (result: number[]) => void означает:

  • Функция принимает один параметр — массив чисел (number[])
  • Функция ничего не возвращает (void)

Более сложные примеры

Рассмотрим несколько сценариев:

Callback с возвратом значения

function transformData(
    data: number[], 
    transformer: (item: number, index: number) => string
): string[] {
    return data.map(transformer);
}

const result = transformData([1, 2, 3], (num, idx) => `Index ${idx}: ${num}`);
// result: ["Index 0: 1", "Index 1: 2", "Index 2: 3"]

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

function fetchData(
    url: string,
    onSuccess: (data: any) => void,
    onError: (code: number, message: string) => void
): void {
    // ... логика запроса
}

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

function processWithCallback(
    data: number[],
    callback?: (result: number[]) => void
): void {
    const result = data.map(x => x * 2);
    if (callback) {
        callback(result);
    }
}

Создание собственных типов для callback

Если один и тот же callback используется в нескольких местах, лучше вынести его в отдельный тип:

type DataCallback = (result: number[]) => void;

function processData(data: number[], callback: DataCallback): void {
    // ...
}

function logData(data: number[], callback: DataCallback): void {
    // ...
}

Или с помощью интерфейса:

interface CallbackInterface {
    (result: number[]): void;
}

function processData(data: number[], callback: CallbackInterface): void {
    // ...
}

Типизация методов объектов

Если callback — метод объекта, можно использовать контекст:

interface EventHandler {
    handleEvent: (event: string) => void;
}

function registerHandler(handler: EventHandler): void {
    handler.handleEvent('click');
}

Проверка на практике

TypeScript будет ругаться, если вы попытаетесь передать функцию с неправильной сигнатурой:

// Ошибка! Функция должна принимать number[], а не string[]
processData([1, 2, 3], (result: string[]) => {}); 

// Ошибка! Функция должна возвращать void, а не number
processData([1, 2, 3], (result) => 42); 

// Ошибка! Слишком много параметров
processData([1, 2, 3], (result, extra) => {}); 

Таким образом, строгая типизация callback'ов в TypeScript полностью решает вашу проблему и даёт все преимущества статической проверки типов.

<<>>

Использование generic-типов для callback

Если вы хотите сделать callback более гибким и переиспользуемым, можно использовать обобщённые типы (generics). Это позволяет создавать функции, которые работают с разными типами данных, сохраняя строгую типизацию.

function processData<T>(
    data: T[], 
    callback: (item: T, index: number) => void
): void {
    data.forEach((item, index) => callback(item, index));
}

// Работает с числами
processData([1, 2, 3], (num, idx) => console.log(num * idx));

// Работает со строками
processData(['a', 'b', 'c'], (str, idx) => console.log(`${idx}: ${str}`));

// Работает с объектами
interface User { name: string; age: number; }
processData<User>(
    [{ name: 'Alice', age: 30 }], 
    (user) => console.log(user.name)
);

Также можно типизировать возвращаемое значение callback:

function mapData<T, R>(
    data: T[], 
    callback: (item: T, index: number) => R
): R[] {
    return data.map(callback);
}

const numbers = mapData([1, 2, 3], (num) => num * 2); // number[]
const strings = mapData([1, 2, 3], (num) => `Number: ${num}`); // string[]

Этот подход особенно полезен, когда вы пишете библиотечные функции или утилиты, которые должны работать с разными типами данных, сохраняя при этом полную типовую безопасность.