Перейти к содержанию

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 на шестнадцать в своей копии и проследите, какая часть результата зависит от этого поля. Затем верните начальные данные, чтобы следующие уроки продолжали общий пример.

Если объект выводится неправильно, сначала проверьте выбранное поле и синтаксис выражения. Если импорт не найден, проверьте относительный путь и экспорт, а не название заголовка. Это два разных класса ошибки. Исходники здесь не исполнялись; ожидаемые признаки нужны для вашей будущей ручной сверки. Следующий шаг вынесет области интерфейса в отдельные компоненты и добавит единые стили.

Оглавление серии