Next-01. Компоненты, JSX и маршруты
Собираем страницу из компонентов и разбираем, как Next.js связывает файлы с адресами.
На этой странице
От JavaScript к интерфейсу
Вы уже умеете хранить данные и писать функции. React – библиотека JavaScript для создания интерфейсов. В ней функция может описывать часть страницы. Такую функцию называют компонентом: она получает данные и возвращает разметку. Например, карточка товара – компонент, который можно использовать для разных товаров.
Next.js строится на React и добавляет устройство приложения: маршруты, общие макеты страниц и работу на сервере. Сначала разберемся с записью интерфейса, затем – с файлами проекта. Руководство React объясняет компоненты подробнее.
JSX: разметка внутри кода
JSX – способ записать разметку прямо в файле JavaScript или TypeScript. Она похожа на HTML, но является частью кода: в нее можно подставлять значения. В проекте с TypeScript файл с JSX имеет расширение .tsx.
export default function HomePage() {
const course = "Веб-разработка";
return <h1>Курс: {course}</h1>;
}HomePage – функция-компонент. return возвращает описание элемента, а фигурные скобки {course} подставляют значение переменной. Браузер покажет заголовок «Курс: Веб-разработка». export default отмечает главный результат файла – Next.js использует его как страницу маршрута. Строку HTML собирать вручную не нужно.
Одна строка и несколько строк
Для короткого выражения достаточно return <h1>Привет!</h1>;. Если разметка занимает несколько строк, оберните все возвращаемое выражение в круглые скобки:
export default function HomePage() {
return (
<main>
<h1>Каталог</h1>
<p>Выберите товар.</p>
</main>
);
}Скобки группируют многострочное выражение и помогают избежать ошибки из-за переноса строки после return. У стрелочной функции есть сокращенная форма – без слова return: const Title = () => <h1>Каталог</h1>;. Для первых страниц удобнее обычная функция: так лучше видно, что она возвращает.
Четыре правила JSX
- Компонент возвращает один корневой элемент. В примере выше это
<main>. Если лишний элемент в документе не нужен, используйте фрагмент<>...</>: он объединяет соседние элементы, но не добавляет обертку в итоговый HTML. - Каждый элемент закрывается. Для элемента без содержимого пишите
<img src="/book.png" alt="Обложка" />или<br />. - CSS-класс задается через
className, а связь подписи с полем – черезhtmlFor: это имена свойств JSX. - Значение JavaScript вставляется в фигурных скобках:
src={imageUrl}. Строку можно написать прямо:src="/book.png".
export default function HomePage() {
const title = "Книги";
return (
<>
<h1 className="text-xl">{title}</h1>
<img src="/book.png" alt="Обложка книги" />
</>
);
}В фигурных скобках можно вычислить выражение, например {price * count}. Команду if внутрь разметки так не вставляют: условие вычисляют до return либо используют условное выражение. Подробнее: правила JSX и JavaScript внутри JSX.
Для адреса /book.png положите изображение в public/book.png. Если файла нет, браузер покажет сломанную картинку.
Повторное использование: компонент и параметры
Чтобы не копировать карточку, передайте ей данные через props – параметры компонента. Имя пользовательского компонента пишут с заглавной буквы. Это отличает <ProductCard /> от встроенного HTML-элемента <article>.
type ProductCardProps = { name: string; price: number };
function ProductCard({ name, price }: ProductCardProps) {
return (
<article>
<h2>{name}</h2>
<p>{price} ₽</p>
</article>
);
}
export default function HomePage() {
return (
<main>
<ProductCard name="Блокнот" price={350} />
<ProductCard name="Карандаш" price={80} />
</main>
);
}type ProductCardProps описывает ожидаемые данные: name – строка, price – число. Если TypeScript пока непривычен, вернитесь к основам TypeScript. Здесь главное увидеть связь: родитель передает параметры, карточка показывает их в JSX.
Условие и список в JSX
Данные часто определяют, что увидит пользователь. Условное выражение выбирает один из двух вариантов, а map превращает элементы массива в разметку:
const products = [
{ id: 1, name: "Блокнот" },
{ id: 2, name: "Карандаш" },
];
export default function HomePage() {
return (
<main>
<h1>Товары</h1>
{products.length === 0 ? (
<p>Пока ничего нет</p>
) : (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
)}
</main>
);
}После ? записан вариант для истинного условия, после : – для ложного. key помогает React отличать элементы списка при обновлении; берите устойчивый идентификатор данных, например id. Если компоненту вообще нечего показывать, он может вернуть null. Подробнее: условия и списки.
Как файл становится страницей
В App Router папки внутри app задают части адреса, а page.tsx – содержимое страницы. В начале файла обычно находится export default function ..., как в примерах выше.
app/
page.tsx → /
about/
page.tsx → /about
products/
[id]/
page.tsx → /products/42[id] – переменная часть адреса: /products/42 и /products/81 используют одну страницу с разными параметрами. layout.tsx задает общую оболочку для вложенных страниц, например шапку и навигацию. Между внутренними страницами обычно переходят через Link из next/link: он позволяет Next.js выполнить переход без полной перезагрузки документа.
import Link from "next/link";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ru">
<body>
<nav>
<Link href="/">Главная</Link>
<Link href="/about">О проекте</Link>
</nav>
{children}
</body>
</html>
);
}children – место для текущей страницы. Тип React.ReactNode обозначает содержимое, которое React умеет выводить. Сверяйте структуру проекта с официальным руководством по страницам и макетам.
Где выполняется код
По умолчанию страница в App Router – серверный компонент: код выполняется на сервере, а браузер получает готовое представление. Для кнопки с обработчиком нажатия нужен клиентский компонент. Поместите директиву "use client"; первой строкой его файла:
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Нажато: {count}</button>;
}useState – функция React для хранения состояния компонента. useState(0) задает начальное число нажатий: count – текущее значение, setCount обновляет его и вызывает новое отображение. onClick связывает кнопку с действием. Директива задает границу этого файла и импортируемых им компонентов, а не переключает все приложение в клиентский режим. Клиентский компонент тоже может получить первоначальный HTML с сервера. В статье о формах появится "use server"; – она помечает функцию, исполняемую на сервере. Подробнее: серверные и клиентские компоненты и директива use server.
Попробуйте сами
- Создайте проект по актуальной инструкции Next.js. Если установщик спрашивает имя папки, точка
.означает текущую папку. - Найдите
app/page.tsx. Измените заголовок, добавьте абзац и подставьте в него переменную через{...}. - Вынесите повторяющуюся часть страницы в компонент с двумя параметрами. Покажите его дважды с разными данными.
- Добавьте страницу
/aboutи ссылку на нее. Убедитесь, что шапка из общегоlayout.tsxостается на обеих страницах. - Сверстайте шапку по макету в Figma.