> Как типизировать компонент и пропсы в TypeScript? (React, TypeScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Исходный Код

Стек: React, TypeScript

> Пример ответа

Короткий ответ

Компонент типизируется через React.FC или явное указание типа пропсов. Пропсы описываются интерфейсом или type, где обязательные поля идут без модификатора, опциональные - с ?. Для children используется React.ReactNode, для event handlers - типы из React (например, React.MouseEvent). Generic-компоненты типизируются через параметры типа. Избегай React.FC, если нужен children - используй PropsWithChildren.

Подробное объяснение

В React с TypeScript типизация компонентов и пропсов решает две задачи: статическая проверка типов на этапе компиляции и автодополнение в IDE. Основные подходы:

  1. Типизация пропсов: создаётся интерфейс или type, где каждое поле имеет тип. Обязательные поля - без ?, опциональные - с ?. Для функций-обработчиков используются типы из React: React.MouseEvent<HTMLButtonElement>, React.ChangeEvent<HTMLInputElement>.

  2. Типизация самого компонента:

    • React.FC<Props> - устаревший подход, добавляет children по умолчанию и неявный return type.
    • const Component = ({ prop1, prop2 }: Props) => ... - явное указание пропсов, более гибко.
    • React.VFC<Props> - без children (устарел с React 18).
  3. Generic-компоненты: когда тип пропсов зависит от переданного типа, используется <T extends ...>(props: Props<T>).

  4. Children: типизируется как React.ReactNode (включает строки, элементы, фрагменты, null). Для строгой типизации - React.ReactElement или кастомный тип.

  5. Ref forwarding: React.forwardRef<RefType, PropsType>.

  6. Default props: через Component.defaultProps или деструктуризацию со значениями по умолчанию.

На практике

Для senior-уровня важно:

  • Использовать discriminated unions для пропсов, которые меняют поведение компонента (например, type Props = { variant: 'primary' } | { variant: 'secondary', secondaryProp: string }).
  • Применять utility types: Pick, Omit, Partial, Required для переиспользования типов.
  • Типизировать render props и children как функции: children: (value: T) => React.ReactNode.
  • Для HOC и render props использовать строгую типизацию с generic.
  • Избегать any в пропсах - использовать unknown с type guards.

Пример кода

TSX
// Базовый компонент
interface ButtonProps {
label: string;
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
children?: React.ReactNode;
}
const Button = ({ label, onClick, variant = 'primary', disabled, children }: ButtonProps) => (
<button onClick={onClick} disabled={disabled} className={`btn-${variant}`}>
{label}
{children}
</button>
);
// Generic-компонент
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
}
const List = <T extends { id: string }>({ items, renderItem }: ListProps<T>) => (
<ul>{items.map((item, index) => <li key={item.id}>{renderItem(item, index)}</li>)}</ul>
);
// Discriminated union для пропсов
type ModalProps =
| { type: 'confirm'; onConfirm: () => void; onCancel: () => void }
| { type: 'alert'; onClose: () => void };
const Modal = (props: ModalProps) => {
if (props.type === 'confirm') {
return <div>Confirm: <button onClick={props.onConfirm}>OK</button></div>;
}
return <div>Alert: <button onClick={props.onClose}>Close</button></div>;
};

Как отвечать на собеседовании

Начни с базового подхода (интерфейс для пропсов, явная типизация), затем переходи к нюансам: почему React.FC не рекомендуется, как типизировать children, как работать с generic. Покажи понимание trade-off между гибкостью и строгостью типизации. Приведи пример discriminated union для сложных пропсов. Упомяни, что для event handlers важно указывать конкретный элемент (HTMLButtonElement, HTMLInputElement), а не generic Event.

Что проверяет интервьюер

  • Понимание разницы между type и interface для пропсов.
  • Умение типизировать children, refs, event handlers.
  • Знание generic-компонентов и discriminated unions.
  • Способность избегать избыточной типизации (например, не использовать React.FC без необходимости).
  • Понимание, как типизация влияет на DX и баги в рантайме.

Типичные ошибки

  • Использование React.FC по умолчанию, что добавляет children даже когда не нужно.
  • Типизация children как JSX.Element вместо React.ReactNode (не включает строки и null).
  • Указание any для event handlers вместо конкретных типов.
  • Игнорирование опциональных пропсов через ? - все поля обязательные.
  • Неправильная типизация generic-компонентов (отсутствие extends для ограничения типа).
  • Использование React.SyntheticEvent вместо конкретного события (например, React.MouseEvent).

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью