JavaScript ловит ошибки в момент запуска — когда программа уже работает у пользователя. TypeScript ловит ошибки раньше: во время написания кода в редакторе. Это меняет то, как чувствуется разработка: вместо «запустил и проверил» получаешь мгновенную подсказку прямо в коде.
В React TypeScript особенно полезен в нескольких местах: пропсы компонентов, события, состояния и ссылки на DOM-элементы. Разберём каждое.
Зачем TypeScript, если уже есть JavaScript
В чистом JavaScript компонент принимает что угодно. Допустим, есть кнопка:
function Button({ variant, onClick, children }) {
return <button className={variant} onClick={onClick}>{children}</button>;
}
Ничто не мешает вызвать её так: <Button variant="primry" /> — опечатка в названии варианта, onClick не передан. JavaScript промолчит. Пользователь увидит сломанную кнопку.
TypeScript добавляет описание того, что компонент ожидает. Передал не то — редактор подчеркнул ошибку ещё до запуска. Это и есть статическая типизация: проверка до выполнения, а не во время.
Типизация пропсов
Пропсы — это данные, которые родитель передаёт компоненту. Описывают их через type или interface. На практике чаще используют type — он чуть гибче.
type ButtonProps = {
variant: "primary" | "secondary";
disabled?: boolean;
onClick: () => void;
children: React.ReactNode;
};
function Button({ variant, disabled = false, onClick, children }: ButtonProps) {
return (
<button className={variant} disabled={disabled} onClick={onClick}>
{children}
</button>
);
}
Несколько деталей:
"primary" | "secondary"— вместо простоstring. Теперьvariant="primry"не пройдёт компиляцию, а редактор предложит варианты.disabled?— вопросительный знак означает, что проп необязательный.React.ReactNode— тип дляchildren: принимает текст, элементы, массивы иnull.
Переиспользуемые компоненты и обобщённые типы
Представьте список товаров. Потом список заказов. Потом список пользователей. Каждый раз писать отдельный компонент списка неудобно — проще один, который работает с любыми данными.
Для этого TypeScript предлагает обобщённые типы (generics). Буква T в угловых скобках — это «любой тип, который передаст вызывающая сторона»:
type ListProps<T> = {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyOf: (item: T) => string;
};
function List<T>({ items, renderItem, keyOf }: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={keyOf(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
Когда используем List<Product>, TypeScript знает: renderItem получает Product, не что-нибудь. Если попробовать обратиться к несуществующему полю — ошибка в редакторе, а не в браузере.
Состояния-с-несколькими-вариантами
Вот распространённая ситуация: компонент грузит данные с сервера. Пока грузит — спиннер, если ошибка — сообщение, если готово — данные. Как это описать?
Первый порыв — набор необязательных флагов:
type State = {
isLoading?: boolean;
error?: string;
data?: Product;
};
Проблема: такой тип допускает бессмысленные комбинации — например, isLoading: true и data: {...} одновременно. TypeScript не возразит, а компонент поведёт себя непредсказуемо.
Решение — дискриминированный union: объединение типов с общим полем-меткой (status), по которому TypeScript понимает, в каком именно состоянии мы находимся.
type RemoteData<T> =
| { status: "loading" }
| { status: "error"; error: string }
| { status: "success"; data: T };
function ProductView({ state }: { state: RemoteData<Product> }) {
switch (state.status) {
case "loading": return <Spinner />;
case "error": return <ErrorBox message={state.error} />;
case "success": return <ProductCard product={state.data} />;
}
}
В каждой ветке switch TypeScript точно знает, какие поля доступны: в ветке "success" есть state.data, в "error" — state.error. Попытка обратиться к полю другой ветки — ошибка компиляции.
Типизация событий и ref
React-события имеют конкретные типы. Тип зависит от того, на каком элементе происходит событие и какой это обработчик.
function SearchInput() {
const inputRef = useRef<HTMLInputElement>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
inputRef.current?.focus();
};
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} onChange={handleChange} />
</form>
);
}
Полезные типы событий:
React.ChangeEvent<HTMLInputElement>— изменение поля вводаReact.FormEvent<HTMLFormElement>— отправка формыReact.MouseEvent<HTMLButtonElement>— клик по кнопке
useRef<HTMLInputElement>(null) — типизированная ссылка на DOM-элемент. Редактор подскажет все доступные методы и свойства элемента.
Производные типы: не повторяй себя
TypeScript позволяет строить новые типы из уже существующих, не копируя их. Три часто встречающихся приёма:
type Product = {
id: string;
name: string;
price: number;
createdAt: string;
};
// Форма создания: всё, кроме серверных полей
type CreateProductInput = Omit<Product, "id" | "createdAt">;
// Форма редактирования: все поля необязательны
type UpdateProductInput = Partial<Product>;
// Только название и цена для карточки-превью
type ProductPreview = Pick<Product, "name" | "price">;
Смысл: если изменится Product, производные типы обновятся автоматически. Один источник правды вместо ручной синхронизации.
Коротко
- TypeScript проверяет типы до запуска — ошибки видны в редакторе, а не у пользователя.
- Пропсы описывают через
typeилиinterface; узкие union строковых литералов вместоstringловят опечатки. - Переиспользуемые компоненты (список, таблица, селект) делают обобщёнными через
<T>, чтобы не терять типы. - Взаимоисключающие состояния описывают дискриминированным union с полем-меткой — это убирает невозможные комбинации.
- Тип события зависит от элемента:
React.ChangeEvent<HTMLInputElement>,React.MouseEvent<HTMLButtonElement>и т.д. useRef<HTMLElement>(null)— типизированная ссылка на DOM.Omit,Partial,Pick— строят новые типы из существующих без дублирования.
Что почитать дальше
- Хуки: кастомные и ловушки useEffect — как типизировать кастомные хуки.
- Формы и валидация — типы для полей формы и схемы валидации.
- Структура проекта и компоненты — как организовать типизированные компоненты в проекте.