0Pricing
React Academy · Урок

Обобщённые компоненты и вспомогательные типы

Пишите переиспользуемые обобщённые компоненты и используйте PropsWithChildren, ComponentProps и Omit.

«Обобщённые компоненты и вспомогательные типы» — бесплатный урок React Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения React Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс React Academy содержит 4 уроков всего.

Зачем нужны обобщённые компоненты

Обобщённые компоненты работают с разными типами данных, сохраняя полную типобезопасность: это может быть типизированный список, элемент выбора или таблица для любой структуры данных.

Простой обобщённый список

Используйте параметр типа T у функции, чтобы создать компонент списка, отображающий элементы любого типа.

interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string;
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, i) => (
        <li key={keyExtractor(item)}>{renderItem(item, i)}</li>
      ))}
    </ul>
  );
}

Ограничение обобщённых типов

Используйте extends, чтобы добавить ограничения и сообщить TypeScript, какими свойствами должен обладать тип.

interface SelectProps<T extends { id: string; label: string }> {
  options: T[];
  value: T | null;
  onChange: (option: T) => void;
}

function Select<T extends { id: string; label: string }>(
  { options, value, onChange }: SelectProps<T>
) {
  return (
    <select value={value?.id} onChange={e => onChange(options.find(o => o.id === e.target.value)!)}>
      {options.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
    </select>
  );
}

PropsWithChildren

React.PropsWithChildren<P> добавляет children?: React.ReactNode к любому типу пропсов — это удобно для компонентов-обёрток.

function Card({ title, children }: React.PropsWithChildren<{ title: string }>) {
  return (
    <div className="card">
      <h2>{title}</h2>
      {children}
    </div>
  );
}

ComponentProps

React.ComponentProps<typeof Component> извлекает пропсы существующего компонента. Это удобно, когда Вы хотите расширить компонент или создать для него обёртку.

import { Button } from './Button';

type ExtendedButtonProps = React.ComponentProps<typeof Button> & {
  loading?: boolean;
};

function LoadingButton({ loading, children, ...props }: ExtendedButtonProps) {
  return (
    <Button {...props} disabled={loading || props.disabled}>
      {loading ? <Spinner /> : children}
    </Button>
  );
}

ComponentPropsWithoutRef

React.ComponentPropsWithoutRef<'input'> извлекает пропсы HTML-элемента без пропса ref — это идеально подходит для создания обёрток над HTML-элементами.

type InputProps = React.ComponentPropsWithoutRef<'input'> & {
  label: string;
  error?: string;
};

function FormInput({ label, error, ...inputProps }: InputProps) {
  return (
    <div>
      <label>{label}</label>
      <input {...inputProps} />
      {error && <span className="error">{error}</span>}
    </div>
  );
}

Исключение и выбор

Используйте Omit<T, Keys>, чтобы удалить определённые пропсы, и Pick<T, Keys>, чтобы оставить только нужные пропсы из существующего типа.

interface UserCardProps {
  id: string;
  name: string;
  email: string;
  avatar: string;
  role: string;
}

// Only expose non-sensitive fields:
type PublicUserCardProps = Omit<UserCardProps, 'email' | 'role'>;
// Or pick exactly what you need:
type AvatarProps = Pick<UserCardProps, 'name' | 'avatar'>;

Частичный и обязательный типы

Partial<T> делает все пропсы необязательными, что удобно для форм обновления. Required<T> делает все пропсы обязательными.

interface UserFormData { name: string; email: string; bio: string; }

// For update: only send changed fields
type UserUpdatePayload = Partial<UserFormData>;

// For create: all fields are mandatory
type UserCreatePayload = Required<UserFormData>;

Record для типизированных словарей

Record<Keys, Value> создаёт тип объекта с определёнными типами ключей. Это удобно для таблиц соответствий и переводов.

type Status = 'idle' | 'loading' | 'success' | 'error';

const statusLabels: Record<Status, string> = {
  idle: 'Ready',
  loading: 'Loading...',
  success: 'Done!',
  error: 'Something went wrong',
};

Типы хуков через возвращаемый тип

Используйте ReturnType<typeof useMyHook>, чтобы вывести возвращаемый тип пользовательского хука без ручного дублирования типа.

function useCounter(initial: number) {
  const [count, setCount] = useState(initial);
  return { count, increment: () => setCount(c => c + 1) };
}

type CounterHook = ReturnType<typeof useCounter>;
// { count: number; increment: () => void }

Обобщённый компонент таблицы

Полностью типобезопасная таблица данных работает с любой структурой строк, выводит ключи столбцов и при этом предоставляет автодополнение.

interface Column<T> {
  key: keyof T;
  header: string;
  render?: (value: T[keyof T], row: T) => React.ReactNode;
}

function DataTable<T extends object>({ data, columns }: { data: T[]; columns: Column<T>[] }) {
  return (
    <table>
      <thead><tr>{columns.map(c => <th key={String(c.key)}>{c.header}</th>)}</tr></thead>
      <tbody>{data.map((row, i) => (
        <tr key={i}>{columns.map(c => <td key={String(c.key)}>{c.render ? c.render(row[c.key], row) : String(row[c.key])}</td>)}</tr>
      ))}</tbody>
    </table>
  );
}

Быстрая проверка

Какой служебный тип удаляет указанные ключи из существующего интерфейса TypeScript?

Повторение

Обобщённые компоненты используют параметры типов, чтобы сохранять типобезопасность для разных структур данных. Служебные типы, такие как Omit, Pick, Partial, ComponentProps и PropsWithChildren, устраняют ручное дублирование типов и делают API компонентов более композиционными.

Часто задаваемые вопросы

Урок «Обобщённые компоненты и вспомогательные типы» бесплатный?

Да — полный текст урока «Обобщённые компоненты и вспомогательные типы» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс React Academy, подпишись на CoddyKit PRO. Курс React Academy содержит 4 уроков всего.

Чему я научусь в уроке «Обобщённые компоненты и вспомогательные типы»?

Пишите переиспользуемые обобщённые компоненты и используйте PropsWithChildren, ComponentProps и Omit. Ты практикуешь React Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать React Academy?

Предыдущий опыт не требуется. React Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Обобщённые компоненты и вспомогательные типы»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке React Academy?

Да. Каждый урок React Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Типизация свойств и возвращаемых типов компонентов
  2. Типизация событий и ссылок в TypeScript
  3. Обобщённые компоненты и вспомогательные типы
  4. Типизация контекста и пользовательских хуков
← Назад к React Academy