Перейти к содержимому
Тема 12 из 13
Теория

Маршруты: React Router

Много страниц в одном приложении

Настоящее приложение — это несколько страниц: главная, каталог, карточка товара, корзина, профиль. Можно было бы хранить «текущую страницу» в состоянии и показывать нужный компонент через if. Но тогда:

  • адрес в браузере не меняется — страницу нельзя добавить в закладки или отправить другу;
  • кнопки «Назад» и «Вперёд» не работают;
  • после обновления страницы пользователь всегда оказывается на главной.

React-приложение — это одностраничное приложение (SPA): браузер загружает один HTML-файл, а дальше JavaScript сам перерисовывает страницу. Чтобы при этом работали адреса, нужна маршрутизация: адрес → какой компонент показать. Самая популярная библиотека для этого — React Router.

Как она работает: при клике по ссылке React Router не перезагружает страницу. Он меняет адрес через History API браузера (history.pushState) и перерисовывает нужные компоненты. Получается быстро, как в приложении, и при этом с настоящими адресами.

Подключение

В проекте библиотеку ставят командой npm install react-router-dom и импортируют нужное:

import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

На нашем сайте она уже подключена как глобальный объект ReactRouterDOM:

const { BrowserRouter, Routes, Route, Link } = ReactRouterDOM;

Всё приложение оборачивают в роутер — обычно в main.jsx:

ReactDOM.createRoot(document.getElementById("root")).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
);

BrowserRouter работает с настоящим адресом браузера. В задачах на сайте код выполняется в песочнице, где адресной строки нет, поэтому там используется MemoryRouter — он хранит адрес в памяти. Всё остальное работает одинаково.

Routes и Route

function App() {
  return (
    <Routes>
      <Route path="/" element={<HomePage />} />
      <Route path="/catalog" element={<CatalogPage />} />
      <Route path="/cart" element={<CartPage />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}
  • <Routes> смотрит на текущий адрес и выбирает один подходящий маршрут.
  • <Route path="…" element={…}> — «если адрес такой, показать этот элемент».
  • path="*" совпадает с любым адресом — это страница 404 для всего, что не подошло.

Всё, что стоит вне <Routes> (шапка, меню, подвал), показывается на всех страницах.

Ссылки: Link и NavLink

Обычный <a href="/cart"> перезагрузит всю страницу и сбросит состояние приложения. Внутри приложения используют Link:

<nav>
  <Link to="/">Главная</Link>
  <Link to="/catalog">Каталог</Link>
  <NavLink to="/cart" className={({ isActive }) => (isActive ? "active" : "")}>
    Корзина
  </NavLink>
</nav>

Link рисует обычный <a>, но перехватывает клик. NavLink — то же самое, плюс знает, активна ли ссылка: удобно подсвечивать текущий пункт меню.

Параметры адреса: useParams

У карточек товаров адреса вида /products/1, /products/2… Не писать же маршрут на каждый товар. Изменяемую часть адреса обозначают двоеточием:

<Route path="/products/:id" element={<ProductPage />} />
const { useParams } = ReactRouterDOM;

function ProductPage() {
  const { id } = useParams();          // для /products/42 → "42"
  const product = products.find((p) => p.id === Number(id));
  if (!product) return <p>Товар не найден</p>;
  return <h1>{product.title}</h1>;
}

Параметры всегда строки: "42", а не 42. Сравнивая с числовым id, преобразуй через Number. Обычно по параметру загружают данные: useFetch(/api/products/${id}) из прошлой темы.

Параметры после ? (/catalog?sort=price&page=2) читают через хук useSearchParams:

const [searchParams, setSearchParams] = useSearchParams();
const sort = searchParams.get("sort");          // "price"
setSearchParams({ sort: "name" });              // адрес станет ?sort=name

Переход из кода: useNavigate

Иногда переходить нужно не по клику на ссылку, а после действия: отправили форму — открыть страницу заказа, вышли из аккаунта — на главную:

const { useNavigate } = ReactRouterDOM;

function OrderForm() {
  const navigate = useNavigate();

  async function handleSubmit(e) {
    e.preventDefault();
    const order = await sendOrder();
    navigate(`/orders/${order.id}`);
  }

  return <form onSubmit={handleSubmit}>…</form>;
}
  • navigate("/path") — перейти (как клик по ссылке, с записью в историю);
  • navigate("/path", { replace: true }) — заменить текущую запись: «Назад» не вернёт сюда;
  • navigate(-1) — назад, как кнопка браузера.

Для перехода прямо во время отрисовки (например, «нет доступа — на страницу входа») есть компонент <Navigate to="/login" replace />.

Вложенные маршруты и Outlet

У большинства страниц общий макет: шапка, меню, подвал. Его описывают родительским маршрутом, а место для дочерних страниц обозначают <Outlet />:

const { Outlet } = ReactRouterDOM;

function Layout() {
  return (
    <div>
      <header>
        <Link to="/">Магазин</Link> <Link to="/cart">Корзина</Link>
      </header>
      <main>
        <Outlet />          {/* сюда подставится текущая страница */}
      </main>
      <footer>© 2026</footer>
    </div>
  );
}

function App() {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route path="/" element={<HomePage />} />
        <Route path="/cart" element={<CartPage />} />
        <Route path="/products/:id" element={<ProductPage />} />
      </Route>
      <Route path="/login" element={<LoginPage />} />   {/* без общего макета */}
    </Routes>
  );
}

Вложенность маршрутов повторяет вложенность интерфейса: Layout остаётся на месте (и сохраняет своё состояние) при переходах между страницами внутри него.

Защищённые страницы

Страницу профиля или заказов нельзя показывать без входа. Частый приём — проверить и перенаправить:

function ProfilePage() {
  const { user } = useAuth();            // свой хук над контекстом из темы про useContext
  if (!user) return <Navigate to="/login" replace />;
  return <h1>Привет, {user.name}</h1>;
}

Важно понимать: это защита интерфейса, а не данных. Настоящую проверку доступа всегда делает сервер — код в браузере пользователь может изменить.

Деплой SPA

Когда приложение выложено на хостинг, адрес /products/42 для сервера — путь к несуществующему файлу. Поэтому хостинг настраивают так, чтобы на любой адрес отдавался index.html, а дальше маршрут выберет React Router. На Netlify, Vercel и Cloudflare Pages для этого есть простая настройка (или она включена по умолчанию для SPA).

Итог

  • SPA: одна HTML-страница, переходы без перезагрузки; React Router связывает адрес с компонентами.
  • <BrowserRouter> вокруг приложения; <Routes> + <Route path element>; path="*" — 404.
  • <Link to> вместо <a href>; NavLink знает, активна ли ссылка.
  • /products/:id + useParams() — параметры адреса (строки!); useSearchParams — после ?.
  • useNavigate() — переход из кода; <Navigate> — перенаправление при отрисовке.
  • Общий макет — родительский Route с <Outlet />.

Две страницы

Лёгкая+10 XP

Напиши компонент App (роутер вокруг него уже есть). В нём:

  • <nav> с двумя ссылками Link: Home (адрес /) и About (адрес /about);
  • маршруты: / показывает <h1>Home</h1>, /about — <h1>About</h1>, любой другой адрес — <h1>Not found</h1>.
/          → Home
клик About → About
/oops      → Not found

Подключи коннектор, чтобы решать задачи

Код задач выполняется на твоём компьютере — так быстрее и надёжнее, чем в браузере. Для этого нужна бесплатная программа-коннектор CodeWithMe. Подключить её нужно один раз.

Ищем коннектор на этом компьютере…

  1. 1
    Скачай коннектор для своей системы (Windows, macOS или Linux).Скачать коннекторНа странице загрузки выбери файл со своей системой в названии, например windows-amd64.
  2. 2Запусти его и выбери папку, где будут лежать твои решения. Python и Node.js он поставит туда сам, когда понадобится.
  3. 3
    Введи ниже код из 6 цифр — он написан в окне коннектора («Код для сайта CodeWithMe»). Если браузер спросит про доступ к локальной сети — разреши.

Коннектор открытый (исходный код на GitHub), работает только на твоём компьютере и выполняет код лишь после ввода кода сопряжения.