Синтаксис стрелочных функций с дженериками (обобщениями) в TypeScript
Основная проблема: конфликт с JSX
Вы почти всё сделали правильно. Главная причина, по которой ваш первый вариант не работает — это конфликт синтаксиса дженериков с JSX. В файлах .tsx угловые скобки <T> интерпретируются как JSX-тег, а не как указание обобщённого типа. Решение — добавить запятую после имени типа или использовать ограничение extends.
Правильный синтаксис для стрелочных функций с дженериками
Вариант 1: Запятая после имени типа (рекомендуется)
const identity = <T,>(arg: T): T => {
return arg;
};
// Использование:
const result = identity<string>("Hello"); // тип string
const num = identity(42); // тип number (вывод типов работает)
Запятая <T,> сообщает TypeScript, что это обобщение, а не JSX-тег. Это самый чистый способ.
Вариант 2: Ограничение через extends
const identity = <T extends unknown>(arg: T): T => {
return arg;
};
Этот вариант тоже работает, но добавляет лишнее ограничение. Используйте его, если вам действительно нужно сузить типы.
Вариант 3: Для нескольких дженериков
const pair = <T, U>(first: T, second: U): [T, U] => {
return [first, second];
};
Реальный пример из React (файл .tsx)
import React from 'react';
// Компонент с дженериком
const ListComponent = <T extends { id: number }>({ items }: { items: T[] }) => {
return (
<ul>
{items.map(item => (
<li key={item.id}>{JSON.stringify(item)}</li>
))}
</ul>
);
};
// Использование
interface User {
id: number;
name: string;
}
const users: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
const App = () => {
return <ListComponent<User> items={users} />;
};
Важные моменты
- Вывод типов работает: если TypeScript может определить тип из аргументов, указывать его явно необязательно.
- В обычных .ts файлах: можно писать
<T>без запятой, но для единообразия лучше всегда добавлять запятую. - Стрелочные функции vs обычные: синтаксис дженериков идентичен, разница только в форме записи функции.
- Ограничения (extends): используйте, когда нужно гарантировать наличие определённых свойств у типа.
Распространённые ошибки
// ❌ Ошибка: T воспринимается как JSX-компонент
const bad = <T>(arg: T) => arg;
// ✅ Правильно: с запятой
const good = <T,>(arg: T) => arg;
// ✅ Правильно: с extends
const good2 = <T extends string | number>(arg: T) => arg;
В итоге: для стрелочных функций с дженериками всегда используйте <T,> (с запятой) или <T extends ...>. Это универсальное решение, которое работает и в .ts, и в .tsx файлах.
Альтернативный подход: объявление типа отдельно
Если вам не нравится синтаксис с запятой или вы хотите сделать код более читаемым, можно вынести дженерик в отдельный тип или интерфейс, а затем использовать его в стрелочной функции.
// Определяем тип функции с дженериком
type IdentityFn = <T>(arg: T) => T;
// Реализуем функцию через этот тип
const identity: IdentityFn = (arg) => arg;
// Использование
const str = identity("hello"); // string
const num = identity(123); // number
Этот подход особенно полезен, когда у вас есть несколько функций с одинаковой сигнатурой дженерика или когда вы хотите переиспользовать тип в разных местах.
Для сложных компонентов React
Если ваш компонент имеет много пропсов с дженериками, можно объединить их в один интерфейс:
interface TableProps<T> {
data: T[];
columns: (keyof T)[];
renderCell: (item: T, column: keyof T) => React.ReactNode;
}
const Table = <T extends Record<string, any>>(props: TableProps<T>) => {
// реализация
};
Такой подход делает код чище и отделяет логику типов от реализации функции.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги