Skip to content

Next-01. Компоненты, JSX и маршруты

Собираем страницу из компонентов и разбираем, как Next.js связывает файлы с адресами.

On this page

От JavaScript к интерфейсу

Вы уже умеете хранить данные и писать функции. React – библиотека JavaScript для создания интерфейсов. В ней функция может описывать часть страницы. Такую функцию называют компонентом: она получает данные и возвращает разметку. Например, карточка товара – компонент, который можно использовать для разных товаров.

Next.js строится на React и добавляет устройство приложения: маршруты, общие макеты страниц и работу на сервере. Сначала разберемся с записью интерфейса, затем – с файлами проекта. Руководство React объясняет компоненты подробнее.

JSX: разметка внутри кода

JSX – способ записать разметку прямо в файле JavaScript или TypeScript. Она похожа на HTML, но является частью кода: в нее можно подставлять значения. В проекте с TypeScript файл с JSX имеет расширение .tsx.

app/page.tsx
export default function HomePage() {
  const course = "Веб-разработка";
 
  return <h1>Курс: {course}</h1>;
}

HomePage – функция-компонент. return возвращает описание элемента, а фигурные скобки {course} подставляют значение переменной. Браузер покажет заголовок «Курс: Веб-разработка». export default отмечает главный результат файла – Next.js использует его как страницу маршрута. Строку HTML собирать вручную не нужно.

Одна строка и несколько строк

Для короткого выражения достаточно return <h1>Привет!</h1>;. Если разметка занимает несколько строк, оберните все возвращаемое выражение в круглые скобки:

app/page.tsx
export default function HomePage() {
  return (
    <main>
      <h1>Каталог</h1>
      <p>Выберите товар.</p>
    </main>
  );
}

Скобки группируют многострочное выражение и помогают избежать ошибки из-за переноса строки после return. У стрелочной функции есть сокращенная форма – без слова return: const Title = () => <h1>Каталог</h1>;. Для первых страниц удобнее обычная функция: так лучше видно, что она возвращает.

Четыре правила JSX

  1. Компонент возвращает один корневой элемент. В примере выше это <main>. Если лишний элемент в документе не нужен, используйте фрагмент <>...</>: он объединяет соседние элементы, но не добавляет обертку в итоговый HTML.
  2. Каждый элемент закрывается. Для элемента без содержимого пишите <img src="/book.png" alt="Обложка" /> или <br />.
  3. CSS-класс задается через className, а связь подписи с полем – через htmlFor: это имена свойств JSX.
  4. Значение JavaScript вставляется в фигурных скобках: src={imageUrl}. Строку можно написать прямо: src="/book.png".
app/page.tsx
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>.

app/page.tsx
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 превращает элементы массива в разметку:

app/page.tsx
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 выполнить переход без полной перезагрузки документа.

app/layout.tsx
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"; первой строкой его файла:

app/counter.tsx
"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.

Попробуйте сами

  1. Создайте проект по актуальной инструкции Next.js. Если установщик спрашивает имя папки, точка . означает текущую папку.
  2. Найдите app/page.tsx. Измените заголовок, добавьте абзац и подставьте в него переменную через {...}.
  3. Вынесите повторяющуюся часть страницы в компонент с двумя параметрами. Покажите его дважды с разными данными.
  4. Добавьте страницу /about и ссылку на нее. Убедитесь, что шапка из общего layout.tsx остается на обеих страницах.
  5. Сверстайте шапку по макету в Figma.