Синтаксис стрелочных функций с дженериками в TypeScript
Проблема с синтаксисом JSX
Ошибка, которую вы видите, возникает из-за того, что TypeScript (и особенно TSX/JSX) интерпретирует <T> как открывающий тег JSX-элемента. Это классическая проблема синтаксической неоднозначности, когда угловые скобки используются и для дженериков, и для JSX.
Основные решения
Есть несколько проверенных способов объявить стрелочную функцию с дженериком:
- Способ 1: Запятая после дженерика — самый популярный и рекомендованный в большинстве проектов:
const identity = <T,>(arg: T): T => {
return arg;
};
// Использование:
const result = identity<string>("hello"); // type: string
const num = identity(42); // type: number (вывод типов работает)
Запятая <T,> сообщает парсеру, что это не JSX-тег, а параметр дженерика. Это самый лаконичный и широко используемый подход.
- Способ 2: Ограничение через
extends— явно указываем, что T — это тип, а не JSX-элемент:
const identity = <T extends unknown>(arg: T): T => {
return arg;
};
// Более практичный пример с ограничением:
const getLength = <T extends { length: number }>(arg: T): number => {
return arg.length;
};
getLength("hello"); // 5
getLength([1, 2, 3]); // 3
// getLength(42); // Ошибка: number не имеет свойства length
- Способ 3: Типизация через переменную — объявляем тип отдельно:
type IdentityFn = <T>(arg: T) => T;
const identity: IdentityFn = (arg) => {
return arg;
};
Реальные примеры использования
Вот более сложные сценарии, где дженерики в стрелочных функциях особенно полезны:
Пример 1: Функция-обёртка для React-компонента
// Создаём HOC (Higher-Order Component) с дженериком
const withLogger = <P extends object>(Component: React.ComponentType<P>) => {
return (props: P) => {
console.log("Props:", props);
return <Component {...props} />;
};
};
// Использование:
interface UserProps { name: string; age: number; }
const UserComponent = ({ name, age }: UserProps) => <div>{name}: {age}</div>;
const UserWithLogger = withLogger(UserComponent);
Пример 2: Типобезопасный обработчик событий
const createEventHandler = <T extends HTMLElement, E extends keyof HTMLElementEventMap>(
element: T,
eventType: E,
handler: (event: HTMLElementEventMap[E]) => void
) => {
element.addEventListener(eventType, handler as EventListener);
return () => element.removeEventListener(eventType, handler as EventListener);
};
// Использование:
const cleanup = createEventHandler(document.body, "click", (event) => {
console.log(event.clientX); // TypeScript знает, что event — MouseEvent
});
Важные замечания
- Вывод типов работает: если TypeScript может вывести тип из аргументов, указывать его явно не обязательно:
identity(42)вернётnumber. - Множественные дженерики: запятая нужна только после первого, если есть риск путаницы с JSX:
<T, U extends T>(a: T, b: U) => .... - В файлах .tsx всегда используйте запятую или
extends— это стандарт для React-проектов. - Производительность: все способы компилируются в одинаковый JavaScript, разница только в синтаксисе.
Лично я рекомендую способ с запятой (<T,>) — он минималистичный, понятный и не создаёт лишних ограничений. Используйте его в качестве основного, а extends — когда нужно явно ограничить допустимые типы.
Альтернативный подход: использование интерфейсов
Если вы предпочитаете более декларативный стиль или работаете с большим количеством однотипных функций, можно вынести сигнатуру дженерика в интерфейс:
interface IIdentity {
<T>(arg: T): T;
}
const identity: IIdentity = (arg) => arg;
// Или с несколькими параметрами:
interface IMap {
<TInput, TOutput>(array: TInput[], fn: (item: TInput) => TOutput): TOutput[];
}
const map: IMap = (array, fn) => array.map(fn);
Когда это полезно
- Переиспользование типов: одна сигнатура для нескольких реализаций.
- Документирование API: интерфейс служит контрактом.
- Совместимость с классами: если функция будет передаваться как метод объекта.
Недостатки
- Дополнительный код: нужно объявлять интерфейс отдельно.
- Меньше гибкости: сложнее добавить новые ограничения на месте.
Этот подход хорошо работает в крупных проектах, где важна читаемость и поддержка типов. Для быстрых утилит лучше использовать синтаксис с запятой.