JSX как выражение, чистые функции-компоненты, props, children, списки, ключи и безопасная композиция.
Компонент — это не шаблон с произвольными побочными эффектами, а типизированная функция, описывающая интерфейс для данного снимка входов.
Вы научитесь проектировать маленькие чистые компоненты, типизировать их входы, сохранять identity списков и собирать интерфейс композицией.
До начала достаточно строгий TypeScript, функции, массивы и неизменяемые преобразования. В конце должен появиться не конспект, а типизированный каталог с семантической разметкой, устойчивыми ключами и переиспользуемыми slots.
JSX компилируется в создание React elements. Фигурные скобки вставляют значение выражения; строки экранируются React DOM, поэтому обычный текст не становится HTML. Теги в нижнем регистре означают intrinsic DOM elements, а имя с заглавной буквы — ссылку на компонент. Возвращаемое дерево должно иметь один корневой element или Fragment.
Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.
type GreetingProps = { name: string };
export function Greeting({ name }: GreetingProps) {
return <p>Здравствуйте, {name}</p>;
}Если name содержит <script>, React отобразит текст. Для интерпретации HTML нужен отдельный опасный API, который рассматривается в теме безопасности.
Рабочая проверка. Передайте строку с HTML-символами и проверьте, что она отображается как текст.
Типичная поломка. Склеивать пользовательские данные в HTML-строку и вставлять через DOM API.
Во время render компонент читает props, state и context и возвращает описание UI. Он не должен менять внешние переменные, отправлять запросы, подписываться или мутировать полученные объекты. Чистота позволяет React повторить, прервать или отбросить render без внешних последствий. Пользовательские действия выполняются в handlers, синхронизация с внешней системой — в Effect или framework boundary.
Рабочая проверка. Отрендерите компонент дважды с одинаковыми входами и проверьте отсутствие новых записей в внешнем массиве.
Типичная поломка. Отправлять аналитику или изменять cache непосредственно в теле компонента.
Props описывают минимальный вход компонента. Не передавайте огромный доменный объект, если компонент использует два поля: это расширяет связь и усложняет переиспользование. Обработчики называют по намерению (onArchive), а DOM-event тип оставляют внутри leaf-компонента. Значения по умолчанию задают при деструктуризации.
Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.
type StatusBadgeProps = {
label: string;
tone?: 'neutral' | 'success' | 'danger';
};
export function StatusBadge({ label, tone = 'neutral' }: StatusBadgeProps) {
return <span data-tone={tone}>{label}</span>;
}Литеральный union ограничивает варианты, а optional prop действительно может отсутствовать. null не добавляется автоматически.
Рабочая проверка. Попробуйте передать неизвестный tone и убедитесь, что ошибка возникает у потребителя компонента.
Типичная поломка. Принимать user: any и читать произвольные поля глубоко внутри визуального компонента.
ReactNode описывает широкий набор отображаемых children: elements, строки, числа, null и коллекции. ReactElement уже и полезен, если нужен именно element для клонирования или инспекции, хотя такая связь часто хрупка. Named slots через props делают структуру явной: header, actions, children. Не добавляйте children автоматически, если компонент их не поддерживает.
Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.
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>;
}Slots позволяют родителю передать поведение и разметку, не превращая Panel в компонент со множеством бизнес-флагов.
Рабочая проверка. Соберите две панели с разными действиями, не меняя реализацию Panel.
Типичная поломка. Добавлять boolean props showEdit, showDelete, showExport для каждой новой кнопки.
key сообщает React идентичность sibling-элемента между render. Он должен быть стабильным и уникальным среди соседей, происходить из данных и не генерироваться во время render. Индекс подходит только для действительно статичного списка без вставок, удаления и перестановки; иначе локальное состояние и DOM могут переехать к другой записи.
Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.
export function ProductList({ products }: { products: readonly Product[] }) {
return <ul>{products.map(product =>
<ProductRow key={product.id} product={product} />
)}</ul>;
}key не попадает в ProductRow как обычный prop. Если идентификатор нужен внутри, передайте id отдельно.
Рабочая проверка. Введите текст в строке, переставьте товары и проверьте, что текст остаётся у правильного id.
Типичная поломка. Использовать индекс или Math.random() как key изменяемого списка.
React-компоненты обычно расширяют поведением через props, children и специализированные wrappers, а не через классовое наследование визуальных компонентов. Контейнер отвечает за layout, feature-компонент — за сценарий, leaf — за DOM. Чем ниже граница, тем меньше она должна знать о маршрутах, кэше и авторизации.
Рабочая проверка. Разделите страницу на data owner, feature composition и чистую визуальную таблицу.
Типичная поломка. Создать BasePage с десятками virtual hooks и скрытым жизненным циклом.
ref как prop в React 19В React 19 function component может получать ref как prop; forwardRef больше не нужен для нового кода и планируется к устареванию. Ref — escape hatch к DOM или императивному handle, а не альтернативный канал данных. Типизируйте конкретный Ref<HTMLInputElement> и сохраняйте обычный controlled flow для значений.
Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.
type SearchInputProps = React.ComponentPropsWithRef<'input'> & {
label: string;
};
export function SearchInput({ label, ref, ...inputProps }: SearchInputProps) {
return <label>{label}<input ref={ref} {...inputProps} /></label>;
}ComponentPropsWithRef сохраняет нативные input props и правильный тип ref. Распространяйте props осознанно, чтобы не переписать защищённые атрибуты.
Рабочая проверка. Сфокусируйте поле после явного пользовательского действия через типизированный ref.
Типичная поломка. Хранить текущее значение input только в ref.current.value и ожидать реактивного render.
Обычная именованная функция с типизированным параметром props обычно даёт лучший вывод и понятные generics. React.FC допустим, но не обязателен и не делает компонент правильнее. Возвращаемый тип часто стоит вывести; явный ReactNode нужен, если API действительно допускает текст или null. Type-only imports уменьшают неоднозначность между runtime и типами.
Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.
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 корректно моделирует такой компонент.
Рабочая проверка. Сравните диагностику generic-компонента, объявленного обычной function и через чрезмерно широкую аннотацию.
Типичная поломка. Добавлять React.FC<any> ко всем компонентам, считая это типобезопасностью.
Реализуйте ProductList, карточку товара и панель действий. Данные приходят как readonly-модель, список поддерживает перестановку, пустое состояние и пользовательский slot действий. Проверьте семантику и сохранение локального состояния строк.
Критерий завершения: компоненты чисты, публичные props не разрешают незаконные сочетания, а reorder не переносит состояние между товарами. Сначала зафиксируйте наблюдаемое поведение тестом или измерением, затем меняйте реализацию.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.
Далее: Модель рендеринга React