JSX и данные карточки
Исходный проект уже отделяет HTML-контейнер, точку входа и компонент App. Теперь добавим первую карточку: «Семантика HTML», тема html, двенадцать минут. Результат урока — шаблон, который получает содержание из объекта, а не из нескольких несвязанных строк. Это первый шаг к каталогу, где одна и та же форма отображения сможет обслуживать разные материалы.
Работаем с полным состоянием lesson-02/after из архива серии. Начальные данные находятся в src/data/notes.js; кроме первой карточки там уже подготовлены CSS и JavaScript, но пока мы выводим только элемент с индексом ноль. Наличие массива не означает, что React автоматически превратит все его объекты в карточки: правило отображения зададим сами.
JSX описывает результат компонента
Внешне JSX напоминает HTML, поэтому в нём удобно узнавать article, заголовок и абзацы. Но это синтаксис внутри JavaScript-модуля. Инструмент преобразует его в JavaScript-представление элементов; React использует это описание для работы с DOM. Браузер не получает наш исходный файл как новый самостоятельный язык разметки и не должен сам понимать расширение JSX.
Компонент-функция возвращает одно выражение. Для нескольких соседних областей можно использовать общий родитель или Fragment. Это не означает, что каждый компонент обязан добавлять лишний div. Выбор оболочки зависит от смысла документа: здесь main обозначает основное содержимое, article — отдельную учебную карточку. Основные правила синтаксиса приведены в документации JSX.
Имя App начинается с заглавной буквы. Так пользовательский компонент отличается от стандартного тега article. Мы экспортируем функцию из модуля и импортируем её в точку входа. Сам файл может содержать другие вычисления, но нельзя забывать, что вызов компонента связан с подготовкой интерфейса. Отправку записи на сервер или изменение глобального документа внутри этого вычисления добавлять не будем.
Один объект и несколько выражений
Полный src/App.jsx теперь выглядит так:
import {initialNotes} from './data/notes.js';
export default function App() {
const note = initialNotes[0];
return <main><h1>Учебный каталог ProfessorWeb</h1>
<article><h2>{note.title}</h2><p>{note.topic} · {note.minutes} минут</p>
<p>{note.body}</p><p>{note.minutes <= 15 ? 'Короткий урок' : 'Подробный урок'}</p></article>
</main>;
}
Импорт даёт доступ к общей константе initialNotes. Переменная note выбирает конкретную запись. Она существует в области функции App, поэтому выражения ниже могут читать её поля. Мы не используем имя, которое случайно было объявлено в другом компоненте, и не рассчитываем на невидимую глобальную переменную.
В фигурных скобках вычисляются JavaScript-выражения. note.title возвращает строку, note.minutes — число, условный оператор — одну из двух поясняющих строк. Если заменить minutes на двадцать, карточка должна получить другой текст о длине урока. Меняется исходное значение, а правило сравнения остаётся тем же. Это полезнее, чем хранить дополнительный флаг короткого материала, который легко забыть обновить.
Между числом и словом «минут» находится обычный текст. Динамический шаблон не обязан целиком состоять из выражений. Наоборот, разделение постоянной подписи и значения помогает увидеть, какая часть зависит от данных. Если написать имя переменной без скобок, получится буквальный текст note.minutes, а не его число; такая ошибка связана с синтаксисом, не с сервером или состоянием.
Что разрешено выводить
Посмотрим на несколько значений в контексте одной карточки. Таблица описывает результат, который ожидается при корректной работе React, а не выполненную нами браузерную проверку.
| Значение | Использование | Смысл для карточки |
|---|---|---|
| Строка title | Содержимое заголовка | Видимый текст |
| Число minutes | Часть абзаца | Видимое число |
| null | Необязательное содержимое | Ничего не добавляет |
| Логическое условие | Управляет веткой JSX | Сам флаг не становится подписью |
| Обычный объект note | Данные для отдельных полей | Нельзя вставить целиком как текстовую карточку |
Неправильный вариант — <h2>{note}</h2>. Объект хранит несколько полей, но не описывает, какое из них должно стать заголовком. Передача объекта допустима в другом качестве, например как prop или объект inline-стилей, однако это иной договор. Различие подробно разобрано в пояснении фигурных скобок.
С массивами также нужна точность. Массив допустимых дочерних значений React может обработать как последовательность, но массив обычных объектов заметок не становится набором готовых карточек. Нужно преобразование каждого объекта в подходящий элемент. Мы выполним его в уроке о списках; сейчас достаточно не называть массив универсальным способом превратить любую структуру в строку.
Выражение в атрибуте
Динамическое значение работает и в свойствах элемента. Например, в учебном варианте короткую подпись можно передать через aria-label или выбрать класс карточки по её теме. Строка в кавычках остаётся строкой; значение после знака равенства внутри фигурных скобок вычисляется. Нельзя одновременно окружить динамическое выражение лишними строковыми кавычками и ждать чтения переменной.
Когда позже добавятся ссылки, адрес карточки будет зависеть от её id. Заголовок при этом не станет идентификатором: два материала могут иметь похожие названия, а изменение текста не должно менять принадлежность записи. Уже сейчас объект хранит html-start отдельно от «Семантика HTML». Таким образом, содержание и техническая идентичность имеют разные обязанности.
Для class используется className, для связанной подписи формы позже появится htmlFor. Это не новая система классов в CSS: итоговый DOM продолжит содержать обычные HTML-атрибуты. В нашем минимальном App класс пока не требуется, поэтому мы не добавляем его только ради демонстрации каждого синтаксического отличия. Разберём стили там, где они действительно организуют экран.
Текст и безопасность вывода
Поле body содержит обычное пояснение о семантике. React выводит строковое содержимое как текст, а не как произвольную HTML-разметку. Если в учебной записи окажется запись <script>, она не должна становиться программой только из-за помещения внутрь абзаца. Не будем превращать body в HTML через опасный обход обычного вывода: для нашего каталога такого требования нет.
Это не означает, что любая строка безопасна в любом месте приложения. Адрес ссылки, действие сервера и название команды имеют собственные правила. Поэтому поля имеют определённые назначения: title — заголовок, body — текст, topic — известная тема, minutes — длительность учебного материала. Позже серверная схема повторит эти границы, а не примет весь произвольный объект на веру.
Полезный неправильный случай — вычислять случайный id при каждом рендере или использовать текущее время как содержание карточки без потребности проекта. Тогда два снимка с теми же учебными данными могут различаться просто из-за нового вызова функции. Для понимания JSX нам достаточно детерминированного объекта: ожидаемое содержание можно сопоставить с исходником по полям.
Сверяем первую карточку
После вашего запуска должны совпасть название «Семантика HTML», тема html и число двенадцать. Подпись «Короткий урок» определяется сравнением с пятнадцатью, а не отдельной захардкоженной строкой для первой записи. Измените только minutes на шестнадцать в своей копии и проследите, какая часть результата зависит от этого поля. Затем верните начальные данные, чтобы следующие уроки продолжали общий пример.
Если объект выводится неправильно, сначала проверьте выбранное поле и синтаксис выражения. Если импорт не найден, проверьте относительный путь и экспорт, а не название заголовка. Это два разных класса ошибки. Исходники здесь не исполнялись; ожидаемые признаки нужны для вашей будущей ручной сверки. Следующий шаг вынесет области интерфейса в отдельные компоненты и добавит единые стили.