Компоненти на кшталт списку, таблиці чи випадного списку працюють з даними будь-якого типу. Без узагальнень доводиться писати any - і зв'язок між даними та колбеками губиться.
Узагальнений компонент:
type ListProps<T> = {
items: T[];
getKey: (item: T) => string | number;
renderItem: (item: T) => ReactNode;
onSelect?: (item: T) => void;
};
function List<T>({ items, getKey, renderItem, onSelect }: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={getKey(item)} onClick={() => onSelect?.(item)}>
{renderItem(item)}
</li>
))}
</ul>
);
}
<List
items={users} // T = User - виведено з items
getKey={(user) => user.id}
renderItem={(user) => user.name} // user: User
onSelect={(user) => open(user.id)}
/>
TypeScript виводить T з переданих даних, і всі колбеки отримують правильний тип. Звернення до неіснуючого поля - помилка компіляції.
Обмеження типу параметра:
function Table<T extends { id: number }>({ rows, columns }: TableProps<T>) { /* ... */ }
Тепер можна використовувати row.id всередині й не передавати getKey.
Колонки, прив'язані до полів даних:
type Column<T> = {
key: keyof T;
header: string;
render?: (value: T[keyof T], row: T) => ReactNode;
};
key: keyof T дозволяє лише існуючі поля - перейменування поля в моделі одразу підсвітить усі таблиці, де воно використовується.
Синтаксис у .tsx: стрілкова функція const List = <T,>(props: ListProps<T>) => ... потребує коми після T - інакше <T> прочитається як JSX-тег. Оголошення function List<T>(...) цієї проблеми не має.
Явний параметр можна передати в JSX, якщо виведення неможливе: <List<User> items={[]} ... />.
Пастки:
memoчиforwardRefгубить узагальнення:memo(List)повертає компонент зT = unknown. Рішення - приведення типу (memo(List) as typeof List) або відмова від обгортки (з React Compiler потреба в ручномуmemoзменшується);- надто складні узагальнення (умовні типи, кілька параметрів) погіршують повідомлення про помилки - для команди простіший тип часто корисніший за максимально точний.