Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як написати узагальнений (generic) компонент React, наприклад список чи таблицю?

Компоненти на кшталт списку, таблиці чи випадного списку працюють з даними будь-якого типу. Без узагальнень доводиться писати 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 зменшується);
  • надто складні узагальнення (умовні типи, кілька параметрів) погіршують повідомлення про помилки - для команди простіший тип часто корисніший за максимально точний.

Докладніше в документації: Узагальнені типи (Generics)

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання