Компоненты и общие стили
Одна карточка уже описана через JSX и данные. Теперь отделим постоянную шапку от содержимого каталога и зададим общие стили. Результат — небольшое дерево компонентов, в котором понятно, где искать навигацию, где менять карточку и какой файл управляет типографикой. Пока не добавляем маршрутизацию: ссылка в шапке ведёт к области того же документа.
Начните со снимка lesson-03/after. В нём App связывает Navbar и Home, а src/main.jsx единожды импортирует styles.css. Структура намеренно проста: мы не превращаем каждое слово заголовка в компонент и не вводим дизайн-библиотеку ради трёх областей. Важно сначала определить обязанности, затем дать им имена и файлы.
Дерево компонентов и дерево DOM
App является корневым компонентом нашего приложения. Внутри него находятся Navbar и main с Home. У React получается дерево компонентов; в браузере возникает связанное с ним дерево DOM-элементов. Эти деревья не обязаны совпадать по числу узлов. Один компонент может вернуть несколько элементов или Fragment, который не добавляет собственную HTML-оболочку.
Navbar представляет верхнюю область с именем сайта и навигацией. Home выбирает первую учебную запись и описывает содержимое каталога. App не должен знать, сколько минут занимает эта конкретная карточка: ему достаточно решить, где находится основное содержимое. Если позднее изменятся поля записи, мы будем разбирать Home и список, а не переписывать точку входа приложения.
Полный файл App после разделения:
import Navbar from './components/Navbar.jsx';
import Home from './pages/Home.jsx';
export default function App() {return <><Navbar /><main><Home /></main></>;}
Пустая обёртка группирует соседние элементы. Она не становится ещё одним div вокруг всей страницы. Импортированные компоненты используются как JSX-теги с заглавной буквы. Это отличается от вызова Navbar() вручную: React должен управлять границами компонентов, их последующими состояниями и жизненным циклом.
Что находится в Navbar
Полная шапка пока содержит одну ссылку на область каталога:
export default function Navbar() {
return <header className="site-header"><strong>Профессор Веб</strong><nav aria-label="Учебная навигация"><a href="#catalog">Каталог</a></nav></header>;
}
Атрибут aria-label уточняет назначение nav. Имя сайта выделено strong, однако главный заголовок содержания остаётся в Home. Если использовать h1 одновременно как декоративный логотип и как заголовок страницы, затем сложнее поддерживать последовательность материалов. Здесь шапка описывает постоянную область, а содержимое каждой страницы сможет получить собственный предметный заголовок.
Ссылка #catalog использует существующий id. Это реальное действие внутри текущего документа, а не обещание будущего маршрута. Мы не ставим сейчас /new, если экран формы ещё не существует. Когда появится роутер, связь изменится явно: внутренние маршруты получат Link, а действительные внешние ссылки останутся обычными ссылками.
Стили не замыкаются внутри файла компонента
Импорт обычного CSS не создаёт автоматическую изоляцию правил. Если styles.css задаёт оформление button, это относится ко всем соответствующим кнопкам в документе. Такое поведение удобно для общего шрифта, цвета текста и признака клавиатурного фокуса. Но слишком широкое правило может случайно затронуть будущую форму или кнопку удаления.
Поэтому постоянные области получают понятные классы: site-header, cards, card, filter. Класс описывает роль, а не порядковый номер сегодняшнего div. Селектор .site-header nav связывает настройку именно с учебной шапкой; общий selector nav имел бы более широкую область действия. Файл помогает организации, однако границы CSS задаёт сам селектор, а не название импортировавшего его компонента.
| Настройка | Где находится | Что меняет |
|---|---|---|
| Системный шрифт | :root | Общую типографику |
| Ограничение ширины | main и site-header | Длину строки и расположение областей |
| Flex и перенос | site-header | Поведение шапки на узком экране |
| Сетка | cards | Количество колонок карточек |
| Видимый фокус | :focus-visible | Ориентир клавиатурной навигации |
Цветовой акцент задан через собственную CSS-переменную. Это позволяет изменить один согласованный цвет, не искать десятки отдельных hex-кодов. Вместе с тем изменение акцента не гарантирует достаточный контраст каждой пары текста и фона. В будущем такую проверку выполняют на реальном экране; здесь мы не выдаём подобранные значения за проведённый аудит доступности.
Системный шрифт и измерения
В учебном каталоге не загружается внешний web font. Используется system-ui и запасное семейство sans-serif. Это уменьшает число отдельных запросов для нашего примера и избегает зависимости от стороннего сервиса шрифтов. Нельзя из этого вывести конкретную экономию миллисекунд: браузер, кеш и размеры ресурсов здесь не измерялись.
Ширина основного содержания ограничена rem-единицами, а боковые отступы остаются внутри области благодаря box-sizing. Длинный заголовок может переноситься, поэтому карточка не предполагает одну фиксированную строку. Для сетки применяется минимум, который не превышает доступную ширину. Это важнее, чем подобрать хорошую картинку только на одном широком рабочем столе.
В теле заметки пока находится обычный абзац. Позднее подробная карточка получит pre-wrap для сохранения переводов строк. Не нужно сразу вставлять переносы через HTML-строку или разбивать каждую строку пользовательского текста отдельным компонентом. Сначала определяем нужное отображение текстовых данных, затем выбираем CSS, который его обеспечивает.
Inline-стиль как отдельный инструмент
Иногда элемент действительно зависит от значения данных: например, учебная диаграмма задаёт ширину полосы. Тогда style получает JavaScript-объект, а названия свойств обычно пишутся camelCase. Двойные фигурные скобки означают выражение JSX и вложенный объект, а не особую магическую команду React. Это различие описано в официальном разборе JSX.
Для постоянного фиолетового цвета шапки inline-стиль не требуется. Вынести его в общий CSS легче, потому что тема и другие состояния интерфейса имеют один источник оформления. Также style не заменяет :hover или :focus-visible автоматически: это задачи CSS. Не следует создавать объект стилей для каждого элемента только потому, что компонент написан на JavaScript.
Неправильный случай — скопировать полный набор правил в Navbar и Home, а затем изменить отступ только в одной копии. Экран может выглядеть почти одинаково, но через несколько уроков начнёт расходиться. Аналогично импорт удалённого CSS-файла приведёт к ошибке инструментария даже при правильном JSX. Нужно согласованно менять и файл, и его импорт.
Проверяем границу изменения
Ожидаемый результат — постоянная шапка, один основной заголовок и первая карточка с общей типографикой. В своей копии измените подпись навигации в Navbar: содержимое body карточки должно остаться в Home и данных. Затем измените общий акцент в CSS: обе подходящие области должны использовать согласованное значение без переписывания компонентов.
На узком экране шапка может переносить элементы, а текст не должен требовать горизонтального чтения. Эти признаки являются критериями будущей ручной проверки, не нашими полученными снимками. Исходники и стили не запускались. В следующем уроке пользовательская кнопка изменит выбранную тему, и дерево компонентов начнёт отображать состояние.