React · Урок 29

React: роутинг и состояние

Одностраничное приложение выросло: уже не один экран. Откроем навигацию по URL без перезагрузки, научимся делить состояние и подгружать данные с сервера — полный набор для реального проекта.

React Router: страницы по URL

Браузерные «страницы» в SPA — это маршруты внутри приложения. URL меняется, страница не перезагружается. Популярное решение — React Router.

npm install react-router-dom

// main.jsx
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Домой</Link>
        <Link to="/about">О нас</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/products/:id" element={<Product />} />
      </Routes>
    </BrowserRouter>
  );
}

Context: общие данные без прокидывания

Корзину нужна в шапке, каталоге и на странице товара. Пробрасывать через props в пять уровней — больно. Context даёт «глобальное» хранилище для состояния приложения.

const CartContext = createContext(null);

function CartProvider({ children }) {
  const [items, setItems] = useState([]);
  const add = (id) =>
    setItems((prev) => [...prev, id]);

  return (
    <CartContext.Provider value={{ items, add }}>
      {children}
    </CartContext.Provider>
  );
}

// в любом компоненте ниже:
function Header() {
  const { items } = useContext(CartContext);
  return <div>В корзине: {items.length}</div>;
}

Данные с сервера: эффекты

Запрос выполняется в useEffect; результат складываем в состояние; пока грузится — показываем спиннер.

import { useEffect, useState } from "react";

function Products() {
  const [list, setList] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("/api/products")
      .then((r) => r.json())
      .then(setList)
      .finally(() => setLoading(false));
  }, []); // [] — выполнить один раз при монтировании

  return loading ? <p>Загрузка…</p> : <ul>
    {list.map((p) => <li key={p.id}>{p.name}</li>)}
  </ul>;
}
ℹ️
Мемоизация useMemo бережёт дорогие вычисления, useCallback — стабильность функций-колбэков. Правило-оберег: не добавляйте их «на всякий случай» — только при измеримой проблеме.
💡
Для больших приложений появляются библиотеки вроде Redux/Zustand. Но правило номер один: начинайте с useState + Context. Большую часть случаев они решают без дополнительных зависимостей.