React: зачем он нужен. Vite, JSX и первый компонент
В уроке 11 вы написали render(state), который заново рисует экран по данным. Это правильная идея, но кода вокруг неё много: найти элемент, создать, заполнить, вставить, не забыть обновить. React берёт эту работу на себя: вы описываете, что должно быть на экране, а как добраться до этого — решает он. Сегодня вы увидите, почему так делают большие сайты, создадите проект на Vite и соберёте страницу из компонентов.
Назвать, чем ручной DOM мешает расти приложению, и сравнить один и тот же интерфейс на чистом JS и на React
Разбить страницу на компоненты и дать им правильные имена
Создать проект командой npm create vite@latest -- --template react и прочитать его структуру
Писать JSX и знать, чем он отличается от HTML: className, htmlFor, закрытые теги, один корень, {…}, style={{…}}
Объявлять компоненты, экспортировать их и импортировать в App.jsx
Попросить ИИ переписать знакомый JS-блок на React и разобрать ответ построчно
Как работать с этой страницей
Примеры на React открываются в песочнице с превью: вкладка App.jsx с кодом, под ней — готовая страница и консоль. Меняйте код и нажимайте ▶ Запустить (или Ctrl + Enter). React подгружается из интернета, поэтому нужен доступ в сеть. В песочнице один файл — компоненты лежат рядом друг с другом; в настоящем проекте на Vite каждый будет в отдельном файле. Строка setTimeout(() => console.log(…), 100) внизу примеров — служебная автопроверка: она печатает в консоль HTML, который построил React, чтобы задачу можно было проверить. В задачах её не удаляйте; в обычном проекте она не нужна. Блоки с заголовком «Терминал» и именем файла — это команды и файлы для вашего компьютера, у них нет кнопки запуска.
01 Зачем React: где ручной DOM начинает мешать
Всё, что вы делали с экраном до сих пор, — это императивный подход: вы пошагово командуете браузеру. «Найди этот элемент. Создай новый. Запиши в него текст. Вставь сюда». Для карточки с заголовком это выглядит так:
Тот же результат на React. Сравните: здесь нет ни одного createElement, ни одного querySelector. Вы пишете то, что хотите увидеть, — функция просто «возвращает» разметку.
Карточка курса: React · Ctrl + Enter
export defaultfunctionApp() {
return (
<div className="card">
<h2>JavaScript с нуля</h2>
<p>24 урока · 8 недель</p>
<button>Записаться</button>
</div>
);
}
// автопроверка: печатает разметку, которую построил ReactsetTimeout(() => console.log(document.getElementById("root").innerHTML), 100);
В консоли обеих песочниц — одна и та же разметка. Разница в том, как вы к ней пришли: в первом случае 12 строк команд, во втором — «картинка» того, что нужно.
Аналогия из жизни
Чойхона. Ручной DOM — это когда вы сами идёте на кухню и говорите повару: «Возьми казан. Налей масло. Положи лук. Через пять минут добавь морковь…» На каждый плов — вся инструкция заново. React — это когда вы говорите официанту: «Плов и чай на двоих». Что именно и в каком порядке делать на кухне — забота заведения. Вы описываете результат (так это называется — декларативный подход), а не шаги.
Четыре боли ручного DOM
1. Разметку не видно
Структура страницы спрятана за десятками createElement и append. Чтобы понять, что получится, нужно мысленно «выполнить» код.
2. Легко забыть обновить
Одни и те же данные показаны в нескольких местах. Поменяли в одном — в другом осталось старое, и экран врёт.
3. Нет «своих тегов»
Карточка курса нужна в десяти местах — приходится писать функции-фабрики и склеивать вручную. Нет единого способа сказать «вот мой блок, используй его как обычный тег».
4. Состояние и экран расходятся
В уроке 11 вы решили это функцией render(), которая перестраивает экран целиком. Работает, но при каждом изменении браузер пересоздаёт всё — и теряются фокус в поле ввода, прокрутка и выделение.
Посмотрите на вторую боль вживую. Цена курса показана в двух местах, а обновлена только в одном:
Экран врёт: забыли обновить второе место · Ctrl + Enter
document.body.innerHTML = `
<p>Курс: <b id="price-top">500</b> сомони</p>
<p>Итого: <b id="price-total">500</b> сомони</p>`;
let price = 500;
price = 450; // сделали скидку
document.querySelector("#price-top").textContent = price;
// про #price-total забыли
console.log(document.querySelector("#price-top").textContent);
console.log(document.querySelector("#price-total").textContent);
Вверху 450, в «Итого» всё ещё 500. В маленькой странице это видно сразу, в большом приложении с сотней экранов — нет. React устроен так, что экран всегда считается из данных, и «забыть» обновить место просто нечем.
Что такое React
React — библиотека для построения интерфейсов, её создала компания Meta (бывший Facebook), и сейчас она, пожалуй, самая популярная в мире для сайтов и приложений. Ключевая идея: интерфейс — это функция от данных. Данные поменялись — React сам сравнит «было» и «стало» и изменит в DOM только то, что нужно. Всё остальное (маршруты, серверный рендеринг) подключается отдельно: на следующем уровне курса вы встретите Next.js — это фреймворк на основе React.
React нужен не всегда
Лендинг из пяти блоков и пары кнопок прекрасно живёт на чистом HTML, CSS и JS — и загружается быстрее. React окупается там, где много интерактивных экранов, одни и те же блоки повторяются, а данные часто меняются: личные кабинеты, магазины, дашборды, чаты. Мы учим React не вместо JS, а поверх него: всё, что вы знаете про функции, массивы и объекты, пригодится на каждой строке.
Практика: закрепляем
Сначала прочитаем ручной DOM, потом починим экран, который «врёт», и наконец переведём код на JSX.
ЗАДАЧА 1.1
Предскажите: что построил код
лёгкая
Сначала предскажите три строки вывода, потом запустите. Обратите внимание, как много кода нужно ради одного списка из трёх пунктов.
Можно редактировать · Ctrl + Enter
const list = document.createElement("ul");
for (const dish of ["Оши палав", "Самбуса", "Курутоб"]) {
const li = document.createElement("li");
li.textContent = dish;
list.append(li);
}
console.log(list.children.length);
console.log(list.firstElementChild.textContent);
console.log(list.outerHTML);
list.children — это дочерние элементы списка. Цикл создаёт по одному li на каждое блюдо.
Подсказка 2
outerHTML показывает элемент вместе с его собственными тегами — строкой, без пробелов между li.
ЗАДАЧА 1.2
Починить «врущий» экран
средняя
Цена показана в трёх местах: «Курс», «Итого» и «Взнос в месяц» (цена, делённая на 4). Допишите render() так, чтобы он обновлял все три места из state.price. Нижние строки менять не нужно: они меняют цену, вызывают render() и печатают результат. Это ровно та работа, которую в React вы не пишете.
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `
<p>Курс: <b id="price-top">500</b> сомони</p>
<p>Итого: <b id="price-total">500</b> сомони</p>
<p>Взнос в месяц: <b id="price-month">125</b> сомони</p>`;
const state = { price: 500 };
functionrender() {
// TODO: обновите все три места по state.price
}
state.price = 480;
render();
console.log(document.querySelector("#price-top").textContent);
console.log(document.querySelector("#price-total").textContent);
console.log(document.querySelector("#price-month").textContent);
Ожидаемый результат
480
480
120
Подсказка 1
Для каждого места: document.querySelector("#id").textContent = …. Три элемента — три строки внутри render.
Подсказка 2
Взнос — это state.price / 4. Число запишется в textContent как строка само.
ЗАДАЧА 1.3
Из createElement в JSX
средняя
Ниже — ручной код, который строит блок тарифа. Верните из App такую же разметку на JSX. Все теги и текст должны совпасть. Класс в JSX пишется как className.
JavaScript
const plan = document.createElement("section");
plan.className = "plan";
const h = document.createElement("h3");
h.textContent = "Тариф «Старт»";
const ul = document.createElement("ul");
const li1 = document.createElement("li");
li1.textContent = "24 урока";
const li2 = document.createElement("li");
li2.textContent = "Проект в портфолио";
ul.append(li1, li2);
plan.append(h, ul);
Откройте любой сайт и мысленно обведите рамками «кирпичики»: шапка, меню, карточка товара, кнопка, подвал. Это и есть компоненты. Компонент — функция, которая возвращает кусок интерфейса. Раз это функция, вы уже умеете с ней обращаться: объявить один раз, вызвать сколько нужно.
Аналогия из жизни
Конструктор или набор формочек для самбусы. Одну форму вы сделали один раз — и лепите ею хоть сто штук. Страница сайта — это не одна большая лепёшка, а набор готовых деталей: шапка, список курсов, подвал. Нужно поменять подвал — меняете в одном месте, и он обновится на всех страницах.
Страница — это дерево
Компоненты вкладываются друг в друга, как вложенные коробки. Самая внешняя — App, главный компонент приложения. Внутри неё лежат остальные:
App
Header
Logo
Nav
Courses
Footer
Одна страница — одно дерево компонентов. В коде оно читается сверху вниз так же, как вложенный HTML
App
├─ Header
│ ├─ Logo
│ └─ Nav
├─ Courses
└─ Footer
Как найти границы компонентов
Блок повторяется (карточка курса, пункт меню) — почти всегда это отдельный компонент.
У блока своя задача («показать шапку», «показать подвал») — отдельный компонент, как отдельная функция в уроке 5.
Файл разросся, и в нём не найти нужное — режем на части.
Не дробите до пылинок. Тег <h2> компонентом делать незачем. Хорошее правило: если для блока можно придумать понятное имя, оно получится и у компонента.
Первый компонент
Компонент выглядит так же, как функция из урока 5, только он возвращает не число, а разметку. Два правила: имя начинается с большой буквы и использовать его нужно как тег:
Обратите внимание на три вещи. <Header /> с большой буквы — это наш компонент: React вызывает функцию Header и подставляет результат. <main> с маленькой — обычный тег HTML. App тоже компонент, просто самый главный. Его мы пишем как export default function App(): именно его песочница (и проект Vite) показывает на странице.
Частая ошибка: имя с маленькой буквы
React различает теги и компоненты по первой букве. <header /> — это тег HTML, а <Header /> — ваша функция. Если назвали компонент header или menu, React не найдёт функцию и нарисует пустой тег (а <menu> — вообще настоящий тег HTML). Ошибки не будет — просто пустота на экране, и это самое неприятное.
Практика: закрепляем
ЗАДАЧА 2.1
Это имя компонента?
лёгкая
Напишите функцию isComponentName(name): она возвращает true, если имя начинается с заглавной латинской буквы, и false — если нет.
Можно редактировать · Ctrl + Enter
functionisComponentName(name) {
// TODO: вернуть true или falsereturnfalse;
}
for (const name of ["Header", "footer", "CourseCard", "nav", "App"]) {
console.log(name + ": " + isComponentName(name));
}
В уроке 17 вы создавали проект на Vite с шаблоном vanilla. Для React меняется один параметр — шаблон. Всё остальное вам знакомо: npm, package.json, dev-сервер.
Аналогия из жизни
Шаблон проекта — как типовая планировка квартиры. Можно самому класть кирпичи и тянуть проводку (настраивать сборщик вручную), а можно получить дом «под ключ» с готовой проводкой и водой и сразу заселяться — добавлять свою мебель. Vite даёт именно такой «дом под ключ» для React.
Пошаговая инструкция
Откройте терминал в папке для проектов (без кириллицы и пробелов в пути; не в OneDrive/iCloud). Node.js уже установлен с урока 17 — проверьте: node -v.
Создайте проект. Часть -- --template react говорит Vite: «возьми шаблон React». Двойное -- передаёт опцию дальше, в create-vite (мы уже видели это на уроке 17).
Перейдите в папку и установите зависимости — npm install скачает React и Vite в node_modules.
Запустите сервер разработки и откройте адрес из терминала.
Терминал
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
Если вы запустите npm create vite@latest без опций, мастер задаст вопросы: название проекта, фреймворк (выберите React), вариант (JavaScript). В зависимости от версии он может ещё предложить сразу установить зависимости и запустить сервер — можно согласиться, а можно ответить «нет» и выполнить команды вручную, как выше. После npm run dev вы увидите:
Терминал — вывод
> my-react-app@0.0.0 dev
> vite
VITE v8.3.4 ready in210 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
Откройте http://localhost:5173/: вы увидите стартовую страницу React со счётчиком. Измените что-нибудь в src/App.jsx и сохраните — страница обновится сама, без перезагрузки (HMR из урока 17).
Если что-то пошло не так
«npm: command not found» — Node.js не установлен или терминал нужно перезапустить. Ошибка про версию Node — обновите Node.js до LTS. Страница пустая — откройте DevTools (F12) → Console: там написано, в каком файле ошибка (чаще всего опечатка в JSX). Порт занят — Vite сам выберет 5174 и напишет об этом.
Что создал Vite
my-react-app/
├─ index.html единственная HTML-страница; внутри — пустой <div id="root">
├─ package.json react и react-dom в dependencies; скрипты dev / build / preview
├─ vite.config.js подключает плагин React: он умеет читать JSX
├─ .gitignore уже содержит node_modules и dist
├─ public/файлы «как есть» (иконка сайта)
└─ src/
├─ main.jsx точка входа: «посадить» React на страницу
├─ App.jsx главный компонент — сюда пишем свой код
├─ App.css стили App
├─ index.css общие стили страницы
└─ assets/картинки, которые импортируются из JS
Точный набор файлов зависит от версии шаблона (например, там могут быть настройки линтера), но три файла всегда одни и те же: index.html, src/main.jsx и src/App.jsx. Рассмотрим их по цепочке.
Страница пустая: только контейнер #root и подключённый модуль main.jsx. Всё остальное нарисует React.
src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
import … from 'react-dom/client' — подключаем часть React, которая умеет работать с браузерным DOM. Имя без ./ — это пакет из node_modules (урок 17).
import App from './App.jsx' — берём главный компонент из соседнего файла. Он экспортирован через export default, поэтому в импорте нет фигурных скобок.
document.getElementById('root') — наш старый знакомый: находим контейнер из index.html. Это последний раз, когда вы вручную ищете элемент в DOM.
createRoot(…).render(…) — «рисуй компонент <App /> внутри этого контейнера». Дальше за всем DOM следит React.
StrictMode — режим подсказок для разработки: он нарочно вызывает компоненты дважды, чтобы быстрее выявить ошибки. В собранном сайте (npm run build) этого нет.
Почему console.log в компоненте печатается дважды
Если в проекте на Vite вы напишете console.log("render Header") внутри компонента, то увидите две одинаковые строки. Это не баг: так работает StrictMode в режиме разработки. В песочнице курса его нет, поэтому здесь каждая строка печатается один раз. Подробнее об этом поговорим на уроке 24.
src/App.jsx (то, что вы увидите в шаблоне — сокращённо)
Сохраните: в браузере появится ваш заголовок. Мы будем писать function App() и отдельной строкой export default App — шаблон Vite делает именно так, а песочница на странице допускает короткий вариант export default function App(). Оба варианта делают одно и то же.
Практика: закрепляем
ЗАДАЧА 3.1
Прочитайте package.json
лёгкая
Ниже — package.json нового проекта, записанный строкой. Разберите его через JSON.parse и выведите три строки: имя проекта, команду скрипта dev и список зависимостей (dependencies) через запятую.
Имя — pkg.name, команда — pkg.scripts.dev. Каждое выведите через console.log.
Подсказка 2
Имена зависимостей — это ключи объекта: Object.keys(pkg.dependencies).join(", ").
ЗАДАЧА 3.2
Порядок команд
средняя
Преподаватель продиктовал команды вперемешку. Отсортируйте массив typed в правильном порядке запуска проекта, ориентируясь на массив order: команда стоит на месте того начала из order, с которого она начинается. Выведите команды по одной в строке.
Можно редактировать · Ctrl + Enter
const order = ["npm create", "cd", "npm install", "npm run dev"];
const typed = [
"npm run dev",
"cd my-react-app",
"npm create vite@latest my-react-app -- --template react",
"npm install",
];
// TODO: отсортируйте typed по порядку из order и выведите
Ожидаемый результат
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
Подсказка 1
Нужна функция, которая по команде находит её позицию: order.findIndex((start) => cmd.startsWith(start)). Сортируем по этой позиции.
Подсказка 2
Осторожно: "npm install" и "npm run dev" оба начинаются с "npm ", но в order стоят более длинные начала — они различаются. Используйте typed.sort((a, b) => pos(a) - pos(b)), где pos — ваша функция позиции.
JSX — это способ писать разметку прямо внутри JavaScript. Браузер JSX не понимает: Vite (а в нашей песочнице — встроенный компилятор) превращает каждый тег в вызов функции. Для вас это значит одно: JSX — это JavaScript, а значит, правила у него строже, чем у HTML.
Что вы пишете и во что это превращается (упрощённо)
// вы пишете:const title = <h1 className="big">Салом</h1>;
// сборщик превращает в обычный JS:const title = jsx("h1", { className: "big", children: "Салом" });
Аналогия из жизни
HTML — это разговорная речь: можно пропустить слово, поставить запятую как угодно, и всё равно поймут. JSX — официальное заявление: каждый тег закрыт, у документа один заголовок, все слова по форме. Зато заявление можно автоматически проверить. Поэтому JSX сразу ругается на опечатку, а браузер молча «догадывается».
В консоли видно, что в итоговой разметке всё стало обычным HTML: class, for. Измените className на class и запустите: в React 19 страница ещё работает, но это считается ошибкой стиля — в правильном JSX пишем className.
Частая ошибка: незакрытый тег
В HTML <input> и <br> закрывать не нужно. В JSX — нужно, иначе компилятор остановится с ошибкой.
Это ошибка: Unexpected token / Unterminated JSX contents
<div>
<input type="text"> {/* ✖ тег не закрыт */}
<br> {/* ✖ и этот тоже */}
</div>
<div>
<input type="text" /> {/* ✓ */}
<br /> {/* ✓ */}
</div>
4. Один корневой элемент
Компонент — это функция, а функция возвращает одно значение. Поэтому два тега рядом вернуть нельзя: их нужно обернуть. Если лишний <div> не нужен, используют фрагмент — пустые теги <>…</>. Он группирует детей, но в итоговом HTML ничего не добавляет.
Две версии: с div и с фрагментом
// ✖ ошибка: "Adjacent JSX elements must be wrapped in an enclosing tag"return (
<h2>Расписание</h2>
<p>Пн, Ср, Пт — 18:00</p>
);
// ✓ с обёрткойreturn (
<div>
<h2>Расписание</h2>
<p>Пн, Ср, Пт — 18:00</p>
</div>
);
// ✓ с фрагментом: лишнего div в HTML не будетreturn (
<>
<h2>Расписание</h2>
<p>Пн, Ср, Пт — 18:00</p>
</>
);
Круглые скобки после return нужны не React, а JavaScript: если после return перейти на новую строку без скобок, JS решит, что возвращать нечего (return;), и вернёт undefined. Скобки — страховка для многострочной разметки.
5. Фигурные скобки: JavaScript внутри разметки
Всё, что внутри { }, — обычное JS-выражение: переменная, арифметика, вызов функции, тернарный оператор ? :. Результат подставляется в разметку. Скобки можно ставить и в тексте, и в значении атрибута.
Переменные, числа, строки, арифметика, вызовы функций, ? :, &&, ||, методы строк и массивов.
Нельзя в {…}
Инструкции: if, for, const x = …. Это не выражения. Условие внутри разметки пишем через ? : или &&; а if и переменные — выше, перед return.
Комментарий в JSX
{/* вот так */} — это пустое выражение с комментарием внутри. Обычный // … внутри разметки превратится в текст на странице.
Что React рисует, а что нет
Значения true, false, null и undefined React не показывает вообще — на этом построено условное отображение. Но число 0 показывается, и об этом часто забывают.
В последней строке вы ожидали пустоту, а получили 0: выражение 0 && … равно 0, а ноль React рисует. Безопасный вариант — сделать условие настоящим булевым: messages > 0 && … или тернарный оператор.
6. style — это объект
В HTML стиль — строка. В JSX style принимает объект, значит, фигурные скобки получаются двойными: внешние — «здесь JS», внутренние — сам объект. Свойства пишутся в camelCase (backgroundColor), числа без единиц считаются пикселями.
Для основного оформления всё равно используйте CSS-классы (className + style.css): style пригодится, когда значение вычисляется из данных (ширина полосы прогресса, цвет по условию).
ИИ-подсказка
Скопированный из интернета HTML — главный источник ошибок JSX. Если вставили готовую вёрстку и страница не открывается, попросите ИИ: «Переведи этот HTML в JSX: замени class и for, закрой одиночные теги, стили-строки переведи в объекты, оберни в один корень. Перечисли, что изменил». Список изменений сверьте с таблицей выше — так вы сразу запоминаете правила.
Практика: закрепляем
ЗАДАЧА 4.1
Переведите HTML в JSX
лёгкая
Ниже — готовая HTML-вёрстка. Переведите её в JSX внутри <div className="form">: замените class и for, закройте одиночные теги.
Четыре замены: for → htmlFor, class → className, <input …> → <input … />, <br> → <br />.
Подсказка 2
Заглушку-комментарий {/* … */} замените четырьмя строками разметки. Порядок атрибутов оставьте как в HTML.
ЗАДАЧА 4.2
Подставьте значения
лёгкая
Вместо знаков ??? подставьте выражения в фигурных скобках: имя студента, цену курса и цену одного урока (цена, делённая на число уроков). Числа писать вручную нельзя — только переменные и арифметика.
Выражения в JSX · Ctrl + Enter
export defaultfunctionApp() {
const student = "Мадина";
const price = 480;
const lessons = 24;
return (
<div>
<h2>Добро пожаловать, ???!</h2>
<p>Курс стоит ??? сомони, это ??? сомони за урок.</p>
</div>
);
}
// автопроверка: печатает разметку, которую построил ReactsetTimeout(() => console.log(document.getElementById("root").innerHTML.replaceAll("><", ">\n<")), 100);
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
<div>
<h2>Добро пожаловать, Мадина!</h2>
<p>Курс стоит 480 сомони, это 20 сомони за урок.</p>
</div>
Подсказка 1
Каждое ??? заменяется на {student}, {price} и т. д. Текст вокруг остаётся как есть.
Подсказка 2
Цена за урок — выражение прямо в скобках: {price / lessons}.
ЗАДАЧА 4.3
Найдите баг: style-строка
средняя
Карточка акции выводится с ошибкой. Исправьте так, чтобы стиль применялся: переведите его в объект (свойства в camelCase, число 12 — пиксели).
Акция недели · Ctrl + Enter
export defaultfunctionApp() {
return (
<div style="background-color: gold; padding: 12px; border-radius: 8px;">
Акция недели
</div>
);
}
// автопроверка: печатает разметку, которую построил ReactsetTimeout(() => console.log(document.getElementById("root").innerHTML), 100);
Нужна обёртка, которая не попадает в HTML. Как называется такая «невидимая» обёртка?
Подсказка 2
Фрагмент: откройте <> перед h2 и закройте </> после p.
ЗАДАЧА 4.5
Предскажите: что React нарисует
лёгкая
Предскажите, что React покажет в абзаце: впишите в prediction весь текст абзаца так, как он появится на странице (пустые скобки [] означают «ничего не нарисовано»). Затем запустите — код сам сравнит ваш ответ с настоящим.
Что нарисует React · Ctrl + Enter
const prediction = ""; // например: "[1] [] [x] …" — по одному значению в каждой паре скобок
export defaultfunctionApp() {
return (
<p>
[{5 + 3}] [{false}] [{null}] [{0}] [{"a" + "b"}] [{true ? "да" : "нет"}] [{undefined}] [{10 > 5}]
</p>
);
}
// автопроверка: сравнивает ваш прогноз с тем, что построил ReactsetTimeout(() => console.log(document.getElementById("root").textContent.trim() === prediction.trim() ? "Верно!" : "Не угадали, подумайте ещё"), 100);
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
Верно!
Подсказка 1
Не рисуются четыре значения: true, false, null, undefined. Число 0 рисуется.
Подсказка 2
Выражение 10 > 5 равно true, а true React не показывает.
ЗАДАЧА 4.6
Ловушка нуля
сложная
В корзине пусто, но вместо ничего на странице появился ноль. Исправьте так, чтобы при пустой корзине выводилось <p>Корзина пуста</p>, а при непустой — <p>Товаров: N</p>. Затем замените cart на ["Самбуса", "Чой"] и убедитесь, что показывается «Товаров: 2» (после проверки верните пустой массив).
Пока компоненты лежали в одном файле. В реальном проекте так не делают: один компонент — один файл. Это те же модули из урока 17: файл экспортирует функцию, другой файл её импортирует. Для компонентов принято export default.
Структура проекта с компонентами
src/
├─ main.jsx
├─ App.jsx собирает страницу из компонентов
└─ components/
├─ Header.jsx export default function Header()
├─ CourseCard.jsx export default function CourseCard()
└─ Footer.jsx export default function Footer()
Имя файла = имя компонента, с большой буквы: Header.jsx, CourseCard.jsx.
Расширение .jsx — признак того, что внутри JSX. Vite требует его для файлов с разметкой.
Папка components — просто договорённость, чтобы не сваливать всё в src/.
import Header from './components/Header.jsx'
import Footer from './components/Footer.jsx'
export defaultfunctionApp() {
return (
<div className="page">
<Header />
<main>Здесь будут курсы</main>
<Footer />
</div>
)
}
Строка import Header from './components/Header.jsx' читается так: «возьми то, что Header.jsx экспортирует по умолчанию, и назови Header». Путь начинается с ./ (рядом с этим файлом), расширение .jsx пишем целиком — как мы договорились на уроке 17. Не было бы export default — импортировать было бы нечего.
Аналогия из жизни
Кухня большого ресторана. Салаты делает холодный цех, горячее — горячий цех, десерты — кондитерский. Каждый цех работает в своём помещении (файле) и отдаёт готовое блюдо (экспорт). Официант-сборщик (App.jsx) ничего не готовит: он берёт блюда из цехов (импорт) и составляет из них заказ на столе. Перепутать цеха нельзя, а новый повар приходит в конкретный цех, а не на всю кухню сразу.
Именованный экспорт тоже работает
Два способа — выберите один и держитесь его
// 1) по умолчанию — один компонент на файл (наш выбор)
export defaultfunctionHeader() { … }
import Header from './components/Header.jsx'// 2) именованный — можно экспортировать несколько штук из файла
export functionHeader() { … }
import { Header } from './components/Header.jsx'
Главное — чтобы экспорт и импорт совпали по стилю: у export default импорт без скобок, у именованного — со скобками. В этом курсе компоненты экспортируем по умолчанию.
Три типичные ошибки импорта
1) Забыли export default — ошибка «does not provide an export named 'default'». 2) Импортировали без ./: import Header from 'components/Header.jsx' — Vite ищет такой пакет в node_modules и не находит. 3) Назвали компонент в импорте с маленькой буквы: import header from … — получится тег, а не ваш компонент (см. раздел 02).
Один компонент — много раз
Главный выигрыш компонентов — повторное использование: объявили один раз, расставили сколько нужно. Изменили компонент — поменялись все копии.
Попробуйте изменить текст внутри CourseCard — изменятся все три карточки. Сразу возникает вопрос: а если нужны разные названия — «HTML», «JavaScript», «React»? Для этого у компонентов есть props, мы разберём их на следующем уроке.
Заглянем на урок вперёд
Два самых важных слова урока 23: props — параметры компонента (как параметры функции из урока 5) и useState — память компонента, которая заставляет React заново рисовать экран при изменении данных. Нажмите на кнопку и посмотрите — код пока просто читайте, объяснять его мы будем на следующем занятии.
Сравните с уроком 11: там были state, render(), querySelector и addEventListener. Здесь — один компонент, и ни одной строки про поиск элементов. Данные лежат в count, экран описан в return, а связку «изменили данные → перерисовали» React обеспечивает сам.
Практика: закрепляем
ЗАДАЧА 5.1
Строки импорта
лёгкая
Напишите функцию importLine(name), которая по имени компонента возвращает строку импорта для проекта на Vite: файл лежит в ./components/, имеет то же имя и расширение .jsx, в конце — точка с запятой.
Можно редактировать · Ctrl + Enter
const components = ["Header", "CourseCard", "Footer"];
functionimportLine(name) {
// TODO: вернуть строку вида import Имя from "./components/Имя.jsx";return"";
}
for (const name of components) {
console.log(importLine(name));
}
Ожидаемый результат
import Header from "./components/Header.jsx";
import CourseCard from "./components/CourseCard.jsx";
import Footer from "./components/Footer.jsx";
Подсказка 1
Имя встречается в строке дважды: после import и в пути. Удобно собрать строку шаблонной строкой в обратных кавычках.
Подсказка 2
return `import ${name} from "./components/${name}.jsx";`;
ЗАДАЧА 5.2
Уберите копипасту
средняя
Три карточки в App набраны копипастой, а компонент CourseCard объявлен, но не используется. Замените три копии на три использования <CourseCard />. Проверка видит, что компонент действительно вызвался три раза: в консоли должны появиться три строки «CourseCard готов».
Лучший способ понять новый подход — взять код, который вы уже понимаете, и сравнить его с версией на React. ИИ здесь — переводчик, а вы — проверяющий. Вы знаете оригинал, значит, сразу видите, где перевод неточен.
Правила курса
1) Сначала 15 минут сами: попробуйте переписать блок на JSX без подсказок — в песочнице или в проекте на Vite. 2) Только потом промпт. 3) Не вставляйте в проект код, который не можете объяснить строчку за строчкой. 4) Результат проверяет запуск, а не уверенный тон ИИ.
Исходный блок на чистом JS
Это блок, похожий на те, что вы писали на уроках 8 и 11: карточка студента строится вручную.
Почему плохо: ИИ не знает, где вы учитесь, какие инструменты у вас есть, и выдаст «всё и сразу» — с состоянием, эффектами и сторонними библиотеками, которых мы ещё не проходили. И вы не получите объяснений.
Контекст: я новичок, второй месяц учу JavaScript, сегодня впервые увидел React. Проект на Vite, шаблон React, JavaScript (не TypeScript), без дополнительных библиотек.
Задача: вот код на чистом JS: [вставьте блок выше]. Перепиши его как один React-компонент App.
Ограничения: только разметка и данные. Без useState, useEffect и обработчиков событий. Без document.querySelector и createElement. Никаких новых пакетов.
Формат ответа: 1) код компонента; 2) таблица «строка в JS — что заменило её в React» — по строке на каждую строку исходного кода; 3) три отличия JSX от HTML, которые встретились в этом примере.
Проверка: скажи, какие ошибки новички чаще всего делают при таком переводе.
Это развёрнутый вариант шаблона из шпаргалки курса: «Вот код на чистом JS: [код]. Перепиши как React-компонент и объясни построчно, что заменило querySelector и addEventListener». Мы добавили контекст, ограничения и формат — и убрали события, которые появятся на уроке 23.
Кто в ком лежит — видно по отступам, не по вызовам
(нет)
function App() { return … } и export default
Блок стал компонентом: его можно вставить как <App /> и использовать повторно
Чек-лист: как проверять ответ ИИ
Имена компонентов с большой буквы и в объявлении, и в использовании.
className, htmlFor, закрытые теги, один корень — таблица из раздела 04.
style — объект, а не строка.
Лишнее. Если ИИ добавил useState, useEffect, axios или styled-components, хотя вы просили без них, — спросите «зачем это здесь?» и попросите убрать.
Запуск. Вставьте в песочницу или проект и откройте консоль: ошибок нет, результат совпадает с оригиналом.
Практика: закрепляем
ЗАДАЧА 6.1
Проверьте «ответ ИИ»: четыре ошибки
сложная
ИИ перевёл карточку студента на React, но допустил четыре ошибки из чек-листа. Найдите их все и исправьте. Страница должна показать карточку студента и под ней абзац «Данные студента». Запускайте после каждой правки и читайте консоль.
Ошибки по порядку: имя компонента с маленькой буквы; class вместо className; style строкой; два элемента в return без общей обёртки.
Подсказка 2
function Profile и <Profile />; className="profile"; style={{ border: "1px solid gray" }}; в App оберните два элемента в <>…</>.
ЗАДАЧА 6.2
Реальный запрос к ИИ
средняя
Возьмите свой блок из урока 8 или 11 (карточка, пункт списка, заголовок страницы) — только без обработчиков событий. Сначала 15 минут переписывайте его на JSX сами. Потом отправьте ИИ промпт по образцу выше с вашим кодом, получите ответ и составьте таблицу «строка в JS — что в React — что изменилось» (как в разборе выше). Положите результат в песочницу, запустите, убедитесь, что экран совпал с оригиналом. Эта задача проверяется преподавателем — отметьте её сами, когда закончите.
Подсказка 1
Если ответ ИИ не запускается, не просите «исправь» вслепую: скопируйте текст ошибки из консоли и спросите, что она означает и в какой строке причина.
Подсказка 2
Таблицу удобно вести в файле NOTES.md — она пригодится в домашнем задании.
07 Практика на уроке: разбиваем страницу на компоненты
Берём готовую страницу «MakeFirstStep», которая целиком написана в одном компоненте App, и режем её на компоненты. Этапов три, каждый заканчивается проверкой в консоли. В конце вы повторите то же в настоящем проекте на Vite — уже по отдельным файлам.
Этап 1вынести Header
→
Этап 2вынести Courses
→
Этап 3вынести Footer и Nav (внутри Header)
HTML на выходе остаётся тем же. Меняется только то, как собрана страница
Как проверяется «разбиение»
Итоговая разметка при разбиении не меняется — поэтому проверка смотрит ещё и на строки render …. Каждый новый компонент должен первой строкой печатать своё имя: console.log("render Header"). React вызывает компоненты по порядку «сверху вниз, вглубь», так что в консоли вы увидите порядок вызовов — и сразу поймёте, что код действительно разрезан на компоненты, а не просто переписан.
Этап 1. Вынесем Header
Перенесите кусок <header>…</header> в отдельную функцию Header и поставьте на его место <Header />.
ЗАДАЧА 7.1
Этап 1: компонент Header
средняя
Создайте компонент Header с первой строкой console.log("render Header"). В нём — вся шапка (заголовок и nav со ссылками). В App вместо шапки поставьте <Header />. Остальное не трогайте.
Образец — компоненты из раздела 02. function Header() { console.log("render Header"); return ( … ); } — в return вставьте весь тег <header> вместе с содержимым.
Подсказка 2
В App на месте шапки остаётся одна строка <Header />. Всё, что было внутри header, теперь живёт в функции Header.
Этап 2. Вынесем Courses
Теперь то же самое с блоком курсов. Он называется Courses.
ЗАДАЧА 7.2
Этап 2: компонент Courses
средняя
Header уже вынесен. Вынесите блок <main> в компонент Courses с первой строкой console.log("render Courses") и поставьте <Courses /> в App.
Действуйте так же, как с Header: функция с большой буквы, console.log первой строкой, весь тег <main> — в return.
Подсказка 2
Порядок строк в консоли — это порядок вызова компонентов: сначала App, затем его дети по очереди — Header, потом Courses.
Этап 3. Footer и вложенный Nav
Компонент может содержать другой компонент. В шапке выделим меню в отдельный Nav — получится дерево из раздела 02.
ЗАДАЧА 7.3
Этап 3: Footer и Nav внутри Header
сложная
Вынесите <footer> в компонент Footer, а <nav> из шапки — в компонент Nav, который используется внутриHeader. У каждого новый console.log("render Footer") и console.log("render Nav") первой строкой. В итоге в консоли порядок вызовов: App, Header, Nav, Courses, Footer.
Footer — как Header на этапе 1. А Nav объявляется отдельно, и Header теперь содержит <h1> и <Nav />.
Подсказка 2
Порядок строк render … зависит от порядка вызова в дереве, а не от порядка объявления функций в файле.
Повторяем в проекте на Vite
Теперь то же самое «по-взрослому» — каждый компонент в своём файле. Откройте проект my-react-app из раздела 03 (или создайте новый), очистите шаблон и сделайте так:
Создайте папку src/components/ и четыре файла в ней: Header.jsx, Nav.jsx, Courses.jsx, Footer.jsx.
В каждый перенесите свой компонент из песочницы и допишите export default.
Там, где компонент использует другой, добавьте import (Header импортирует Nav).
В App.jsx импортируйте Header, Courses, Footer и соберите страницу.
Стили из вкладки style.css перенесите в src/App.css и убедитесь, что App.jsx его импортирует: import './App.css'.
Запустите npm run dev, откройте консоль браузера и проверьте: строки render … печатаются, и (из-за StrictMode) каждая — дважды.
src/components/Header.jsx
import Nav from './Nav.jsx'
export defaultfunctionHeader() {
return (
<header className="top">
<h1>MakeFirstStep</h1>
<Nav />
</header>
)
}
src/App.jsx
import './App.css'
import Header from './components/Header.jsx'
import Courses from './components/Courses.jsx'
import Footer from './components/Footer.jsx'
export defaultfunctionApp() {
return (
<div className="page">
<Header />
<Courses />
<Footer />
</div>
)
}
Дерево компонентов в браузере
Установите расширение React Developer Tools для вашего браузера: в DevTools появится вкладка Components, где видно то самое дерево — App → Header → Nav и так далее. Это быстрый способ убедиться, что разбиение сделано так, как задумано.
08 Домашнее задание: страница из 5 компонентов
Соберите свою страницу на React: личную визитку, страницу кафе, афишу мероприятия — на ваш выбор. Это первый проект на React + Vite. Модуль «React Lite» — бонусный, оценка в журнал не ставится, но разбор по критериям ниже вы получите от преподавателя.
Что нужно сделать
☐ Создать проект командой npm create vite@latest с шаблоном React (JavaScript), очистить шаблон: убрать счётчик, картинки и лишние стили.
☐ Страница состоит минимум из 5 компонентов, каждый в своём файле в src/components/, с export default.
☐ Минимум один компонент вложен в другой (например, Logo внутри Header), и минимум один используется на странице два раза и больше.
☐ App.jsx только собирает страницу из компонентов: у него нет «своей» большой разметки.
☐ В JSX использованы className, htmlFor (если есть форма), закрытые теги, выражения в {…} (например, год в подвале) и хотя бы один style={{…}} или условный вывод.
☐ Свои стили в App.css или отдельных .css файлах; страница выглядит аккуратно и на телефоне (ширина 390 px).
☐ npm run build проходит без ошибок. В репозитории нет node_modules и dist, есть package-lock.json.
☐ Не менее 3 осмысленных коммитов; репозиторий на GitHub.
☐ README.md: что за страница, как запустить, дерево ваших компонентов и 2–3 предложения своими словами: чем компонентный подход удобнее ручного DOM.
ИИ в домашнем задании
Правило курса: 15 минут сами. Сначала нарисуйте дерево компонентов на бумаге. Потом можно попросить ИИ: «Вот моё дерево компонентов [дерево]. Какие блоки я могу вынести в отдельные компоненты и что здесь можно переиспользовать?» — и сравнить со своим. Код, который не можете объяснить построчно, в проект не вставляйте. В README напишите, где использовали ИИ и что пришлось исправить.
Что сдавать
Ссылку на репозиторий GitHub в чат группы.
Скриншот страницы и скриншот вкладки Components (React Developer Tools) с деревом компонентов.
В сообщении: сколько компонентов у вас получилось и какой из них вы используете повторно.
Разбор по критериям (для самопроверки)
Критерий
Баллы
Проект на Vite запускается, npm run build проходит, шаблон очищен
2
5+ компонентов в отдельных файлах, верные имена, export default и import
3
Есть вложенность и повторное использование компонента; App.jsx только собирает страницу
2
JSX написан правильно: className, закрытые теги, выражения, один корень
1
README с деревом компонентов и своими словами; аккуратные коммиты
2
Баллы нужны для самопроверки: бонусный модуль не влияет на итоговую оценку курса.
✓ Шпаргалка урока
Ручной DOM — императив: «найди, создай, вставь, обнови». Много кода, легко забыть обновить место, разметки не видно. React — декларатив: описываем, что должно быть на экране, а React сам меняет DOM.
Компонент — функция, которая возвращает JSX. Имя с большой буквы, использование как тег: <Header />. Имя с маленькой буквы — это обычный HTML-тег.
Страница = дерево компонентов. Режем по смыслу: повторяющийся блок, блок со своей задачей. Один компонент — один файл.
Проект:npm create vite@latest my-app -- --template react → cd my-app → npm install → npm run dev.