TypeScript: функции как параметры со строгой типизацией — полный разбор

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

TypeScript: функции как параметры со строгой типизацией — полный разбор с примерами

Привет, коллега! Если ты когда-нибудь писал на JavaScript и передавал функцию в другую функцию (например, в setTimeout или Array.map), ты уже знаком с концепцией функций высшего порядка. Но когда дело доходит до TypeScript, возникает логичный вопрос: "Можно ли строго типизировать функцию, которую я передаю как параметр?". Ответ — да, и это одна из самых мощных фишек языка.

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

Кстати, этот вопрос — один из самых популярных на Stack Overflow (тысячи просмотров). Так что если ты хочешь разобраться раз и навсегда — ты по адресу.

Почему это важно: строгая типизация спасает время

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

Вот пример, который не сработает без типов:

// JavaScript (без типов) — ошибка будет только в рантайме
function processData(callback) {
    const result = callback(10, 20);
    console.log(result);
}

// Ой, передали функцию с одним параметром
processData((x) => x * 2); // NaN — потому что второй параметр undefined

С TypeScript мы можем явно указать, какую сигнатуру ожидает колбэк:

// TypeScript — ошибка на этапе компиляции
function processData(callback: (a: number, b: number) => number): void {
    const result = callback(10, 20);
    console.log(result);
}

// ✅ Правильно
processData((x, y) => x + y); // 30

// ❌ Ошибка: Type '(x: number) => number' не присваивается типу '(a: number, b: number) => number'
// processData((x) => x * 2);

Круто, правда? Теперь давай разберем все способы объявления таких типов.

Способ 1: Инлайн-сигнатура (самый простой)

Самый прямолинейный способ — указать сигнатуру функции прямо в параметрах. Это удобно, когда функция-параметр используется только один раз.

// Функция, которая принимает другую функцию с двумя числами и возвращает строку
function formatNumbers(
    a: number,
    b: number,
    formatter: (x: number, y: number) => string
): string {
    return formatter(a, b);
}

// Использование
const result = formatNumbers(5, 10, (x, y) => `Сумма: ${x + y}`);
console.log(result); // "Сумма: 15"

Плюсы: быстро, наглядно.
Минусы: если сигнатура повторяется, придется дублировать код.

Способ 2: Тип-алиас для функции (переиспользуемый)

Если ты используешь одну и ту же сигнатуру в нескольких местах, лучше вынести её в тип-алиас. Это делает код чище и легче поддерживать.

// Объявляем тип функции
type Callback = (error: Error | null, data: string) => void;

// Функция, которая принимает Callback
function fetchData(url: string, callback: Callback): void {
    // Имитация асинхронного запроса
    setTimeout(() => {
        if (url) {
            callback(null, "Данные получены");
        } else {
            callback(new Error("URL пустой"), "");
        }
    }, 1000);
}

// Использование
fetchData("/api/data", (err, data) => {
    if (err) {
        console.error("Ошибка:", err.message);
    } else {
        console.log("Успех:", data);
    }
});

Обрати внимание: мы использовали void как возвращаемый тип. Это значит, что функция может ничего не возвращать (или возвращать что угодно, но мы это игнорируем).

Способ 3: Интерфейс с call signature (для сложных случаев)

Иногда нужно описать функцию, у которой есть ещё и свойства (как у объекта). Для этого подходит интерфейс с call signature.

interface Comparator {
    (a: number, b: number): number;
    description?: string; // дополнительное свойство
}

function sortArray(arr: number[], comparator: Comparator): number[] {
    return arr.sort(comparator);
}

// Создаем функцию с дополнительным свойством
const ascending: Comparator = (a, b) => a - b;
ascending.description = "По возрастанию";

const sorted = sortArray([3, 1, 4, 2], ascending);
console.log(sorted); // [1, 2, 3, 4]
console.log(ascending.description); // "По возрастанию"

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

Способ 4: Дженерики (универсальные функции-параметры)

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

// Функция, которая принимает массив и функцию-преобразователь
function mapArray<T, U>(arr: T[], transform: (item: T) => U): U[] {
    return arr.map(transform);
}

// Пример с числами
const numbers = [1, 2, 3];
const doubled = mapArray(numbers, (n) => n * 2);
console.log(doubled); // [2, 4, 6]

// Пример со строками
const strings = ["a", "b", "c"];
const uppercased = mapArray(strings, (s) => s.toUpperCase());
console.log(uppercased); // ["A", "B", "C"]

// Пример с изменением типа
const lengths = mapArray(strings, (s) => s.length);
console.log(lengths); // [1, 1, 1]

Дженерики <T, U> гарантируют, что тип входного и выходного значения согласованы. Это мощный инструмент для функционального программирования.

Способ 5: Параметры по умолчанию и опциональные колбэки

Иногда колбэк может быть необязательным. TypeScript позволяет это легко обработать с помощью опционального параметра (?).

function loadData(url: string, callback?: (data: string) => void): void {
    // Имитация загрузки
    const data = "Важные данные";
    
    if (callback) {
        callback(data);
    } else {
        console.log("Данные загружены, но колбэк не передан");
    }
}

// Без колбэка
loadData("/data"); // "Данные загружены, но колбэк не передан"

// С колбэком                

Блоги

Рекомендаций по книгам