JSX как выражение, чистые функции-компоненты, свойства, `children`, списки, ключи и безопасная композиция.
Компонент — это не шаблон с произвольными побочными эффектами, а типизированная функция, описывающая интерфейс для данного снимка входов.
Вы научитесь проектировать небольшие чистые компоненты, типизировать их входные данные, сохранять связь элементов списка с данными и собирать интерфейс композицией.
Понадобятся строгий TypeScript, функции, массивы и неизменяемые преобразования. Итог урока — типизированный каталог с семантической разметкой, устойчивыми ключами и переиспользуемыми областями содержимого.
JSX преобразуется в вызовы, создающие элементы React. Фигурные скобки вставляют значение выражения; React DOM экранирует строки, поэтому обычный текст не становится HTML. Тег в нижнем регистре означает DOM-элемент, а имя с заглавной буквы — компонент. Возвращаемое дерево должно иметь один корневой элемент или Fragment.
// src/Greeting.tsx
type GreetingProps = { name: string };
export function Greeting({ name }: GreetingProps) {
return <p>Здравствуйте, {name}</p>;
}Если name содержит <script>, React отобразит текст. Для интерпретации HTML нужен отдельный опасный API, который рассматривается в теме безопасности.
Проверьте себя. Передайте строку с HTML-символами и проверьте, что она отображается как текст.
Частая ошибка. Склеивать пользовательские данные в HTML-строку и вставлять через DOM API.
Во время отрисовки компонент читает свойства, состояние и контекст, а затем возвращает описание интерфейса. Он не должен менять внешние переменные, отправлять запросы, создавать подписки или изменять полученные объекты. Тогда React сможет безопасно повторить, прервать или отбросить вычисление. Пользовательские действия выполняются в обработчиках, а синхронизация с внешней системой — в эффекте или средствами выбранного фреймворка.
Проверьте себя. Отрендерите компонент дважды с одинаковыми входами и проверьте отсутствие новых записей в внешнем массиве.
Частая ошибка. Отправлять аналитику или изменять кеш непосредственно в теле компонента.
Свойства описывают минимальный вход компонента. Не передавайте большой объект предметной области, если нужны только два поля: это усиливает связанность и мешает переиспользованию. Обработчики называют по намерению (onArchive), а тип события DOM оставляют внутри конечного визуального компонента. Значения по умолчанию задают при деструктуризации.
// src/StatusBadge.tsx
type StatusBadgeProps = {
label: string;
tone?: 'neutral' | 'success' | 'danger';
};
export function StatusBadge({ label, tone = 'neutral' }: StatusBadgeProps) {
return <span data-tone={tone}>{label}</span>;
}Объединение строковых литералов ограничивает варианты, а необязательное свойство действительно может отсутствовать. null не добавляется автоматически.
Проверьте себя. Попробуйте передать неизвестный tone и убедитесь, что TypeScript показывает ошибку в месте использования компонента.
Частая ошибка. Принимать user: any и читать произвольные поля глубоко внутри визуального компонента.
ReactNode описывает всё отображаемое дочернее содержимое: элементы, строки, числа, null и коллекции. ReactElement уже и нужен, когда требуется именно элемент. Именованные свойства header, actions и children делают структуру компонента явной. Не добавляйте children в тип автоматически, если компонент не принимает содержимое.
// src/Panel.tsx
type PanelProps = {
title: string;
actions?: React.ReactNode;
children: React.ReactNode;
};
export function Panel({ title, actions, children }: PanelProps) {
return <section><header><h2>{title}</h2>{actions}</header>{children}</section>;
}Именованные области позволяют родителю передать поведение и разметку, не превращая Panel в компонент с множеством флагов.
Проверьте себя. Соберите две панели с разными действиями, не меняя реализацию Panel.
Частая ошибка. Добавлять логические свойства showEdit, showDelete, showExport для каждой новой кнопки.
key помогает React сопоставить элемент списка с записью между отрисовками. Ключ должен приходить из данных, оставаться стабильным и быть уникальным среди соседей. Индекс допустим лишь для неизменяемого списка без вставок, удаления и перестановки; иначе локальное состояние и DOM могут переехать к другой записи.
// src/ProductList.tsx
export function ProductList({ products }: { products: readonly Product[] }) {
return <ul>{products.map(product =>
<ProductRow key={product.id} product={product} />
)}</ul>;
}key не попадает в ProductRow как обычное свойство. Если идентификатор нужен внутри, передайте id отдельно.
Проверьте себя. Введите текст в строке, переставьте товары и проверьте, что текст остаётся у правильного id.
Частая ошибка. Использовать индекс или Math.random() как key изменяемого списка.
React-компоненты расширяют через свойства, дочернее содержимое и специализированные обёртки, а не через классовое наследование. Контейнер отвечает за расположение, компонент сценария — за поведение, а конечный визуальный компонент — за DOM. Чем ниже уровень, тем меньше он должен знать о маршрутах, кеше и авторизации.
Проверьте себя. Разделите страницу на владельца данных, компонент сценария и чистую визуальную таблицу.
Частая ошибка. Создать базовую страницу с десятками переопределяемых методов и скрытым жизненным циклом.
ref как свойство в React 19В React 19 функциональный компонент может получать ref как свойство; в новом коде forwardRef больше не нужен. ref даёт императивный доступ к DOM, но не заменяет обычную передачу данных. Указывайте конкретный тип, например Ref<HTMLInputElement>, а отображаемые значения храните в состоянии.
// src/SearchInput.tsx
type SearchInputProps = React.ComponentPropsWithRef<'input'> & {
label: string;
};
export function SearchInput({ label, ref, ...inputProps }: SearchInputProps) {
return <label>{label}<input ref={ref} {...inputProps} /></label>;
}ComponentPropsWithRef сохраняет нативные свойства поля и правильный тип ref. Распространяйте свойства осознанно, чтобы не перезаписать защищённые атрибуты.
Проверьте себя. Сфокусируйте поле после явного пользовательского действия через типизированный ref.
Частая ошибка. Хранить отображаемое значение поля только в ref.current.value и ожидать новой отрисовки.
Обычная именованная функция с типизированным параметром свойств обычно даёт лучший вывод типов и понятные обобщения. React.FC допустим, но не обязателен и не делает компонент правильнее. Тип результата чаще стоит оставить выводимым; явный ReactNode нужен, если компонент действительно может вернуть текст или null. Импорты через import type явно отделяют типы от кода времени выполнения.
// src/When.tsx
import type { ReactNode } from 'react';
type WhenProps = { condition: boolean; children: ReactNode };
export function When({ condition, children }: WhenProps) {
return condition ? children : null;
}Функция может вернуть ReactNode; современная связка React types и TypeScript корректно моделирует такой компонент.
Проверьте себя. Сравните диагностику обобщённого компонента, объявленного обычной функцией и через чрезмерно широкую аннотацию.
Частая ошибка. Добавлять React.FC<any> ко всем компонентам, считая это типобезопасностью.
Реализуйте ProductList, карточку товара и панель действий. Данные доступны только для чтения, список поддерживает перестановку, пустое состояние и переданную извне область действий. Проверьте семантику и сохранение локального состояния строк.
Работа готова, когда компоненты чисты, публичные свойства не разрешают недопустимые сочетания, а перестановка не переносит состояние между товарами.
Далее: Модель рендеринга React