Стрелочные функции TypeScript с дженериками: полный гайд с примерами

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

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

Начать курс

Меньше кода, больше пользы: разбираемся со стрелочными функциями и дженериками в TypeScript

Вы когда-нибудь писали небольшую утилиту на TypeScript, которая должна была работать и с числами, и со строками, и с массивами? И тут же сталкивались с проблемой: как сделать так, чтобы тип данных был гибким, но при этом TypeScript не ругался? Ответ — дженерики (generics, или обобщённые типы). А если вы любите лаконичный синтаксис, то наверняка используете стрелочные функции.

Но вот незадача: на Stack Overflow вопрос «What is the syntax for Typescript arrow functions with generics?» набрал тысячи просмотров. Оказывается, даже опытные разработчики иногда путаются в этом, казалось бы, простом сочетании. В этой статье мы разберёмся, как правильно писать TypeScript arrow functions с дженериками, рассмотрим частые ошибки и покажем, как сделать ваш код чище и безопаснее.

Готовы? Поехали!

Что такое дженерики в стрелочных функциях?

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

Стрелочные функции (arrow functions) — это компактный синтаксис функций в JavaScript и TypeScript. Когда вы объединяете их с дженериками, получаете мощный инструмент для создания переиспользуемых утилит.

// Обычная функция с дженериком
function identity<T>(arg: T): T {
    return arg;
}

// Стрелочная функция с дженериком — как это выглядит?
const arrowIdentity = <T>(arg: T): T => arg;

Всё просто? Да, но есть нюансы, которые мы разберём ниже.

Базовый синтаксис: как правильно объявить TypeScript arrow function с generics

Самый простой и распространённый способ — указать тип-параметр (дженерик) перед круглыми скобками с аргументами:

const firstElement = <T>(arr: T[]): T | undefined => {
    return arr[0];
};

// Использование
const num = firstElement([1, 2, 3]); // type: number | undefined
const str = firstElement(['a', 'b']); // type: string | undefined

Здесь <T> — это объявление дженерика. TypeScript автоматически выведет тип T на основе переданного аргумента. Если вы явно хотите указать тип, это тоже работает:

const num = firstElement<number>([1, 2, 3]); // Явно указываем тип

Важно: В JSX (React) этот синтаксис может конфликтовать с разметкой. Для таких случаев есть обходные пути (о них ниже).

Несколько дженериков в одной стрелочной функции

Вы можете использовать несколько типов-параметров:

const pair = <T, U>(first: T, second: U): [T, U] => {
    return [first, second];
};

const result = pair(42, 'hello'); // type: [number, string]

Это особенно полезно при работе с утилитами вроде map, reduce или при создании обёрток.

Проблемы с JSX (React) и их решение

Если вы пишете на React с TypeScript, то заметите, что синтаксис <T>(arg) компилятор может интерпретировать как JSX-тег. Это классическая проблема, из-за которой вопрос на Stack Overflow набрал такую популярность.

Есть два основных решения:

1. Использовать extends (даже если не нужно)

Добавьте extends unknown или extends Record<string, never>:

const myFunction = <T extends unknown>(arg: T): T => {
    return arg;
};

Это явно указывает TypeScript, что <T> — это дженерик, а не JSX.

2. Использовать синтаксис с запятой

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

const myFunction = <T,>(arg: T): T => {
    return arg;
};

Запятая не влияет на логику, но решает проблему с парсингом JSX.

Также можно использовать ключевое слово function вместо стрелочной функции, если синтаксис становится слишком запутанным.

Типичные ошибки и как их избежать

Даже опытные разработчики иногда допускают эти ошибки. Давайте разберём их.

Ошибка 1: Забываем указать дженерик в типе

// ❌ Неправильно
const getLength = (arr: T[]): number => arr.length; // T не определён

// ✅ Правильно
const getLength = <T>(arr: T[]): number => arr.length;

Дженерик нужно объявить до того, как вы его используете.

Ошибка 2: Путаем синтаксис с обычной функцией

// ❌ Неправильно (синтаксис обычной функции)
const func = function<T>(arg: T): T { return arg; }; // Это корректно, но не стрелка

// ✅ Стрелочная функция
const func = <T>(arg: T): T => arg;

Разница только в положении дженерика: у стрелочных функций он стоит перед скобками, а не после ключевого слова function.

Ошибка 3: Неправильное использование в коллбэках

Иногда нужно передать стрелочную функцию с дженериком как коллбэк. Здесь важно правильно типизировать переменную:

// ❌ Неправильно
const processArray = <T>(arr: T[], callback: (item: T) => void): void => {
    arr.forEach(callback);
};

// ✅ Правильно — TypeScript сам выведет тип T из массива
const processArray = <T>(arr: T[], callback: (item: T) => void): void => {
    arr.forEach(callback);
};

В этом примере всё корректно, но если вы попытаетесь явно указать дженерик в месте вызова, будьте внимательны с синтаксисом.

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

Давайте рассмотрим несколько реальных сценариев.

Пример 1: Обёртка для асинхронных запросов

const fetchData = <T>(url: string): Promise<T> => {
    return fetch(url).then(response => {                

Блоги

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