Типизация React-компонентов
Типизация React-компонентов в TypeScript — описание интерфейса Props, типов state и хуков, что позволяет проверять корректность передаваемых данных, автодополнять props в JSX и безопасно рефакторить компоненты.
Зачем нужно
Без типизации props ошибки (передача number вместо string, пропуск обязательного prop) ловятся только в рантайме. TypeScript проверяет props при написании JSX, что исключает целый класс ошибок.
Где используется
- Function components с props
- Компоненты с generic данными: списки, таблицы, формы
- Хуки: useState, useReducer, useContext
- Forwardref компоненты
- HOC и render props паттерны
Основной контент
Function component с props
import { FC, ReactNode } from "react";
interface ButtonProps {
label: string;
onClick: => void;
disabled?: boolean;
variant?: "primary" | "secondary" | "danger";
children?: ReactNode;
}
// Вариант 1: FC<Props> (менее предпочтителен — скрывает возвращаемый тип)
const Button: FC<ButtonProps> = ({ label, onClick, disabled, variant = "primary" }) => (
<button onClick={onClick} disabled={disabled} className={`btn-${variant}`}>
{label}
</button>
);
// Вариант 2: явная типизация параметра (предпочтительно)
function Button({ label, onClick, disabled = false, variant = "primary" }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled} className={`btn-${variant}`}>
{label}
</button>
);
}
Компонент с children
import { ReactNode, PropsWithChildren } from "react";
// Вариант 1: явный children в Props
interface CardProps {
title: string;
children: ReactNode;
}
// Вариант 2: PropsWithChildren утилита
type CardProps2 = PropsWithChildren<{ title: string }>;
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div>{children}</div>
</div>
);
}
Generic компонент
interface ListProps<T> {
items: T;
renderItem: (item: T, index: number) => ReactNode;
keyExtractor: (item: T) => string;
emptyMessage?: string;
}
function List<T>({ items, renderItem, keyExtractor, emptyMessage = "No items" }: ListProps<T>) {
if (items.length === 0) return <p>{emptyMessage}</p>;
return (
<ul>
{items.map((item, i) => (
<li key={keyExtractor(item)}>{renderItem(item, i)}</li>
))}
</ul>
);
}
// Использование — TypeScript выводит T из items
<List
items={users}
keyExtractor={(u) => u.id}
renderItem={(u) => <span>{u.name}</span>}
/>
useState и useReducer
import { useState, useReducer } from "react";
// useState — тип выводится из начального значения
const [count, setCount] = useState(0); // number
const [name, setName] = useState(""); // string
// Явный тип для nullable
const [user, setUser] = useState<User | null>(null);
// useReducer с typed action
type Action =
| { type: "increment" }
| { type: "decrement" }
| { type: "reset"; payload: number };
function counterReducer(state: number, action: Action): number {
switch (action.type) {
case "increment": return state + 1;
case "decrement": return state - 1;
case "reset": return action.payload;
}
}
const [count2, dispatch] = useReducer(counterReducer, 0);
dispatch({ type: "reset", payload: 10 }); // типобезопасно
Ref и forwardRef
import { useRef, forwardRef, Ref } from "react";
// Ref на DOM элемент
function SearchInput() {
const inputRef = useRef<HTMLInputElement>(null);
const focus = () => inputRef.current?.focus();
return <input ref={inputRef} type="search" />;
}
// forwardRef
interface InputProps {
placeholder?: string;
onChange: (value: string) => void;
}
const Input = forwardRef<HTMLInputElement, InputProps>(
({ placeholder, onChange }, ref) => (
<input
ref={ref}
placeholder={placeholder}
onChange={(e) => onChange(e.target.value)}
/>
)
);
Частые ошибки
- Использовать
React.FCповсеместно —FCделаетchildrenнеявно опциональным в старых версиях и скрывает возвращаемый тип; лучше явная аннотация параметра. - Передавать
anyвместоReactNode— дляchildrenиспользуйтеReactNode, для элемента —ReactElement, для рендерабельного —ReactNode. - Не типизировать event handlers в JSX —
onClick={handler}— TypeScript выведет тип из пропа элемента; явный тип нужен только для отдельных функций. - Инициализировать
useStateбез типа приnull—useState(null)даёт типnull, неUser | null; нужен явный generic.
Связанные темы
- Типизация Event Handlers
- Generic функции
- interface -- объявление и использование
- _MOC TypeScript
- _MOC JavaScript