> Как типизировать компонент и пропсы в 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. Основные подходы:
-
Типизация пропсов: создаётся интерфейс или type, где каждое поле имеет тип. Обязательные поля - без
?, опциональные - с?. Для функций-обработчиков используются типы из React:React.MouseEvent<HTMLButtonElement>,React.ChangeEvent<HTMLInputElement>. -
Типизация самого компонента:
React.FC<Props>- устаревший подход, добавляет children по умолчанию и неявный return type.const Component = ({ prop1, prop2 }: Props) => ...- явное указание пропсов, более гибко.React.VFC<Props>- без children (устарел с React 18).
-
Generic-компоненты: когда тип пропсов зависит от переданного типа, используется
<T extends ...>(props: Props<T>). -
Children: типизируется как
React.ReactNode(включает строки, элементы, фрагменты, null). Для строгой типизации -React.ReactElementили кастомный тип. -
Ref forwarding:
React.forwardRef<RefType, PropsType>. -
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).
> Похожие задачи по frontend
Как передать все пропсы родительского компонента дочернему без явного перечисления в React
Зачем в setState передавать callback вместо значения
Работали ли вы с React?
В каких случаях использовать useState и Redux и почему
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью