Вивчай

React Router

React Router — дорожнє перехрестя з вказівниками напрямків до різних пунктів призначенняReact Router — дорожнє перехрестя з вказівниками напрямків до різних пунктів призначення

У попередніх уроках ми будували компоненти, працювали зі state та context. Але наш додаток -- це одна сторінка. Що, якщо потрібна навігація: головна, профіль, список товарів, сторінка товару? Для цього існує React Router -- бібліотека для маршрутизації в React-додатках.


Встановлення

npm install react-router-dom

React Router працює тільки на клієнті (SPA -- Single Page Application). Сервер завжди віддає один index.html, а React Router перемикає компоненти залежно від URL.


Базова структура

// src/App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Contact from './pages/Contact'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </BrowserRouter>
  )
}

export default App
  • BrowserRouter -- обгортка, що стежить за URL
  • Routes -- контейнер для маршрутів
  • Route -- один маршрут: path -- URL, element -- що рендерити

Сторінки -- звичайні компоненти

// src/pages/Home.tsx
export default function Home() {
  return (
    <div>
      <h1>Головна сторінка</h1>
      <p>Ласкаво просимо!</p>
    </div>
  )
}

// src/pages/About.tsx
export default function About() {
  return (
    <div>
      <h1>Про нас</h1>
      <p>Ми вчимо React!</p>
    </div>
  )
}

Замість HTML-тега <a> використовуй <Link> -- він переходить між сторінками без перезавантаження:

import { Link } from 'react-router-dom'

function Navigation() {
  return (
    <nav>
      <Link to="/">Головна</Link>
      <Link to="/about">Про нас</Link>
      <Link to="/contact">Контакти</Link>
    </nav>
  )
}

NavLink -- з активним станом

NavLink автоматично додає клас active для поточного маршруту:

import { NavLink } from 'react-router-dom'

function Navigation() {
  return (
    <nav>
      <NavLink
        to="/"
        className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
      >
        Головна
      </NavLink>
      <NavLink
        to="/about"
        className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
      >
        Про нас
      </NavLink>
    </nav>
  )
}
Увага

Ніколи не використовуй <a href="/about"> для внутрішньої навігації в React-додатку. Тег <a> перезавантажить сторінку та скине весь React-state. Використовуй <Link> або <NavLink>.


Layout з навігацією

Зазвичай навігація та footer однакові на всіх сторінках. Створимо layout:

// src/components/Layout.tsx
import { NavLink, Outlet } from 'react-router-dom'

export default function Layout() {
  return (
    <div className="app">
      <header>
        <nav>
          <NavLink to="/">Головна</NavLink>
          <NavLink to="/about">Про нас</NavLink>
          <NavLink to="/products">Товари</NavLink>
        </nav>
      </header>

      <main>
        <Outlet /> {/* Тут рендериться поточна сторінка */}
      </main>

      <footer>
        <p>Footer</p>
      </footer>
    </div>
  )
}
// src/App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Layout from './components/Layout'
import Home from './pages/Home'
import About from './pages/About'
import Products from './pages/Products'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
          <Route path="products" element={<Products />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}
  • <Route path="/" element={<Layout />}> -- Layout обгортає всі вкладені маршрути
  • <Outlet /> -- місце, де рендериться вміст вкладеного маршруту
  • <Route index> -- маршрут для / (головна сторінка)

Динамічні маршрути та useParams

Для сторінок на кшталт /products/42 або /users/john:

// src/App.tsx
<Route path="products/:productId" element={<ProductDetail />} />

:productId -- динамічний параметр. Отримуємо його через useParams:

// src/pages/ProductDetail.tsx
import { useParams } from 'react-router-dom'
import { useState, useEffect } from 'react'

interface Product {
  id: number
  title: string
  price: number
  description: string
}

export default function ProductDetail() {
  const { productId } = useParams<{ productId: string }>()
  const [product, setProduct] = useState<Product | null>(null)

  useEffect(() => {
    async function fetchProduct() {
      const res = await fetch(`https://fakestoreapi.com/products/${productId}`)
      const data = await res.json()
      setProduct(data)
    }
    fetchProduct()
  }, [productId])

  if (!product) return <p>Завантаження...</p>

  return (
    <div>
      <h1>{product.title}</h1>
      <p>{product.description}</p>
      <p className="price">{product.price} $</p>
    </div>
  )
}

Посилання на динамічну сторінку

import { Link } from 'react-router-dom'

function ProductList({ products }: { products: Product[] }) {
  return (
    <ul>
      {products.map(p => (
        <li key={p.id}>
          <Link to={`/products/${p.id}`}>{p.title}</Link>
        </li>
      ))}
    </ul>
  )
}

useNavigate -- програмна навігація

Для переходу після дії (submit форми, клік кнопки):

import { useNavigate } from 'react-router-dom'

function LoginForm() {
  const navigate = useNavigate()

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    // ... перевірка логіну ...

    // Перехід на головну
    navigate('/')

    // Або перехід назад
    // navigate(-1)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input placeholder="Email" />
      <input type="password" placeholder="Пароль" />
      <button type="submit">Увійти</button>
    </form>
  )
}

Сторінка 404

// src/pages/NotFound.tsx
import { Link } from 'react-router-dom'

export default function NotFound() {
  return (
    <div>
      <h1>404 -- Сторінку не знайдено</h1>
      <p>Такої сторінки не існує.</p>
      <Link to="/">На головну</Link>
    </div>
  )
}
// src/App.tsx
<Routes>
  <Route path="/" element={<Layout />}>
    <Route index element={<Home />} />
    <Route path="about" element={<About />} />
    <Route path="products" element={<Products />} />
    <Route path="products/:productId" element={<ProductDetail />} />
    <Route path="*" element={<NotFound />} /> {/* Будь-який інший URL */}
  </Route>
</Routes>

path="*" -- "зловити все" -- спрацьовує, коли жоден інший маршрут не підходить.


Nested routes -- вкладені маршрути

<Routes>
  <Route path="/" element={<Layout />}>
    <Route index element={<Home />} />

    {/* Вкладені маршрути для settings */}
    <Route path="settings" element={<SettingsLayout />}>
      <Route index element={<SettingsGeneral />} />
      <Route path="profile" element={<SettingsProfile />} />
      <Route path="security" element={<SettingsSecurity />} />
    </Route>

    <Route path="*" element={<NotFound />} />
  </Route>
</Routes>
// src/pages/SettingsLayout.tsx
import { NavLink, Outlet } from 'react-router-dom'

export default function SettingsLayout() {
  return (
    <div className="settings">
      <aside>
        <NavLink to="/settings">Загальні</NavLink>
        <NavLink to="/settings/profile">Профіль</NavLink>
        <NavLink to="/settings/security">Безпека</NavLink>
      </aside>
      <div className="settings-content">
        <Outlet />
      </div>
    </div>
  )
}

Це дає URL: /settings, /settings/profile, /settings/security. Кожен з бічною навігацією.


Структура проекту з routing

src/
├── components/
│   ├── Layout.tsx
│   ├── Navigation.tsx
│   └── ...
├── pages/
│   ├── Home.tsx
│   ├── About.tsx
│   ├── Products.tsx
│   ├── ProductDetail.tsx
│   ├── NotFound.tsx
│   └── settings/
│       ├── SettingsLayout.tsx
│       ├── SettingsGeneral.tsx
│       └── SettingsProfile.tsx
├── contexts/
│   └── ThemeContext.tsx
├── App.tsx
└── main.tsx

Підсумок

  • React Router -- бібліотека для client-side routing у SPA
  • BrowserRouterRoutesRoute -- базова структура
  • Link/NavLink -- навігація без перезавантаження. NavLink має isActive
  • Outlet -- місце рендерингу вкладеного маршруту
  • useParams -- отримати параметри з URL (:productId)
  • useNavigate -- програмна навігація (після submit, за умовою)
  • path="*" -- 404 сторінка
  • Nested routes -- для складних layouts з суб-навігацією

Що далі?

У фінальному уроці блоку ми поєднаємо все вивчене у практичному проекті -- повноцінний React-додаток з API, routing, state management та context.

Інфо

Корисні посилання: