Lekcja 19: React Router - Podstawy Routingu
Większość aplikacji webowych składa się z wielu "stron" lub widoków. W tradycyjnych aplikacjach wielostronicowych (MPA - Multi-Page Applications), nawigacja między stronami powoduje pełne przeładowanie strony przez przeglądarkę. W aplikacjach jednostronicowych (SPA - Single Page Applications), takich jak te budowane w React, chcemy zapewnić płynną nawigację między widokami bez przeładowywania strony. Do tego służy routing po stronie klienta (client-side routing), a najpopularniejszą biblioteką do jego implementacji w React jest React Router.
Czym jest React Router?
React Router to biblioteka, która synchronizuje interfejs użytkownika aplikacji React z adresem URL w przeglądarce. Pozwala definiować różne widoki (komponenty) dla różnych ścieżek URL i zarządzać nawigacją między nimi bez konieczności odświeżania całej strony.
Instalacja
Do aplikacji webowych React instalujemy pakiet react-router-dom
:
npm install react-router-dom
Lub używając yarn:
yarn add react-router-dom
Podstawowe Komponenty React Router (v6)
React Router v6 wprowadził kilka zmian w porównaniu do poprzednich wersji. Kluczowe komponenty to:
<BrowserRouter>
: Komponent, który powinien owijać całą aplikację (lub jej część, która wymaga routingu). Wykorzystuje HTML5 History API (pushState
,replaceState
,popstate
) do synchronizacji UI z URL. Jest to najczęściej używany router dla aplikacji webowych.<Routes>
: Kontener dla definicji poszczególnych tras (<Route>
). Renderuje tylko pierwszy pasujący<Route>
.<Route>
: Definiuje mapowanie między ścieżką URL (path
) a komponentem UI (element
), który ma zostać wyrenderowany, gdy ścieżka pasuje.<Link>
: Komponent służący do tworzenia linków nawigacyjnych. Zamiast standardowego tagu<a href="...">
, używamy<Link to="...">
. Kliknięcie<Link>
zmienia URL i renderuje odpowiedni<Route>
bez przeładowania strony.<NavLink>
: Specjalny rodzaj<Link>
, który wie, czy jest "aktywny" (czy jego ścieżkato
pasuje do aktualnego URL). Przydatne do stylizowania aktywnych linków w menu nawigacyjnym.
Podstawowa Konfiguracja Routingu
// src/index.js lub src/main.jsx (zależnie od konfiguracji projektu)
import React from \"react\";
import ReactDOM from \"react-dom/client\";
import { BrowserRouter } from \"react-router-dom\";
import App from \"./App\";
const root = ReactDOM.createRoot(document.getElementById(\"root\"));
root.render(
<React.StrictMode>
<BrowserRouter> { /* Owiń App w BrowserRouter */ }
<App />
</BrowserRouter>
</React.StrictMode>
);
// src/App.jsx
import React from \"react\";
import { Routes, Route, Link, NavLink } from \"react-router-dom\";
// Przykładowe komponenty stron
function Home() { return <h2>Strona Główna</h2>; }
function About() { return <h2>O Nas</h2>; }
function Contact() { return <h2>Kontakt</h2>; }
function App() {
const navLinkStyles = ({ isActive }) => {
return {
fontWeight: isActive ? \"bold\" : \"normal\",
textDecoration: isActive ? \"underline\" : \"none\",
marginRight: \"10px\"
};
};
return (
<div>
<nav>
<NavLink to=\"/\" style={navLinkStyles}>Home</NavLink>
<NavLink to=\"/about\" style={navLinkStyles}>O Nas</NavLink>
<NavLink to=\"/contact\" style={navLinkStyles}>Kontakt</NavLink>
</nav>
<hr />
{/* Definicja tras */}
<Routes>
<Route path=\"/\" element={<Home />} />
<Route path=\"/about\" element={<About />} />
<Route path=\"/contact\" element={<Contact />} />
</Routes>
</div>
);
}
export default App;
Dynamiczne Trasy i Parametry URL (`useParams`)
Często potrzebujemy tras, które zawierają dynamiczne segmenty, np. ID produktu czy nazwę użytkownika. Definiujemy je za pomocą dwukropka (:
) w atrybucie path
.
Aby uzyskać dostęp do wartości tych parametrów w komponencie renderowanym przez trasę, używamy Hooka useParams
.
import { useParams } from \"react-router-dom\";
function UserProfile() {
// useParams zwraca obiekt z parametrami URL
const { userId } = useParams();
// Nazwa właściwości (userId) musi pasować do nazwy parametru w path (\":userId\")
return <h2>Profil użytkownika o ID: {userId}</h2>;
}
// W definicji tras w App.jsx:
// <Route path=\"/users/:userId\" element={<UserProfile />} />
// Link do takiej trasy:
// <Link to=\"/users/123\">Profil użytkownika 123</Link>
Trasy Zagnieżdżone (`<Outlet />`)
React Router pozwala na tworzenie zagnieżdżonych struktur tras, co jest przydatne przy budowaniu layoutów z częściami wspólnymi (np. panel boczny) i częściami zmieniającymi się.
Komponent nadrzędnej trasy renderuje komponent <Outlet />
, który działa jak placeholder - w tym miejscu zostanie wyrenderowany komponent pasującej trasy podrzędnej.
import { Outlet, Link } from \"react-router-dom\";
// Komponent layoutu dla sekcji /dashboard
function DashboardLayout() {
return (
<div style={{ display: \"flex\" }}>
<nav style={{ borderRight: \"1px solid #ccc\", padding: \"10px\", width: \"150px\" }}>
<h3>Dashboard</h3>
<ul style={{ listStyle: \"none\", padding: 0 }}>
<li><Link to=\"/dashboard\">Przegląd</Link></li>
<li><Link to=\"/dashboard/settings\">Ustawienia</Link></li>
</ul>
</nav>
<main style={{ padding: \"10px\", flexGrow: 1 }}>
{/* Tutaj renderują się komponenty tras podrzędnych */}
<Outlet />
</main>
</div>
);
}
function DashboardOverview() { return <h4>Przegląd Dashboardu</h4>; }
function DashboardSettings() { return <h4>Ustawienia Dashboardu</h4>; }
// W definicji tras w App.jsx:
// <Route path=\"/dashboard\" element={<DashboardLayout />}>
// {/* Trasy podrzędne - ścieżki są względne do rodzica */}
// <Route index element={<DashboardOverview />} /> { /* index oznacza domyślną trasę dla /dashboard */}
// <Route path=\"settings\" element={<DashboardSettings />} /> { /* Pełna ścieżka: /dashboard/settings */}
// </Route>
Nawigacja Programatyczna (`useNavigate`)
Czasami potrzebujemy przekierować użytkownika do innej trasy w odpowiedzi na jakieś zdarzenie (np. po zalogowaniu, po wysłaniu formularza), a nie tylko po kliknięciu linku. Do tego służy Hook useNavigate
.
import { useNavigate } from \"react-router-dom\";
function LoginForm() {
const navigate = useNavigate();
const handleLogin = () => {
// Symulacja logowania...
console.log(\"Zalogowano!\");
// Przekierowanie do dashboardu po zalogowaniu
navigate(\"/dashboard\");
// Można też użyć navigate(-1) do cofnięcia się o jedną stronę w historii
// lub navigate(\"/profile\", { replace: true }) do zastąpienia bieżącej strony w historii
};
return (
<div>
<h3>Logowanie</h3>
{/* ... pola formularza ... */}
<button onClick={handleLogin}>Zaloguj</button>
</div>
);
}
Brak Dopasowania (No Match / 404)
Aby obsłużyć sytuację, gdy żadna z zdefiniowanych tras nie pasuje do aktualnego URL, możemy dodać trasę z path="*"
na końcu definicji <Routes>
.
function NotFound() { return <h2>404 - Strona nie znaleziona</h2>; }
// W definicji tras w App.jsx:
// <Routes>
// <Route path=\"/\" element={<Home />} />
// <Route path=\"/about\" element={<About />} />
// {/* ... inne trasy ... */}
// <Route path=\"*\" element={<NotFound />} /> { /* Ta trasa pasuje do wszystkiego innego */}
// </Routes>
Ćwiczenie praktyczne
Pokaż rozwiązanie
// src/index.js lub src/main.jsx
import React from \"react\";
import ReactDOM from \"react-dom/client\";
import { BrowserRouter } from \"react-router-dom\";
import App from \"./App\";
const root = ReactDOM.createRoot(document.getElementById(\"root\"));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
// src/App.jsx
import React from \"react\";
import { Routes, Route, Link, NavLink, useParams } from \"react-router-dom\";
// Komponenty stron
function Home() { return <h2>Witaj na stronie głównej!</h2>; }
function Products() {
return (
<div>
<h2>Produkty</h2>
<ul>
<li><Link to=\"/products/1\">Produkt 1</Link></li>
<li><Link to=\"/products/2\">Produkt 2</Link></li>
<li><Link to=\"/products/abc\">Produkt ABC</Link></li>
</ul>
</div>
);
}
function ProductDetail() {
const { productId } = useParams();
return <h2>Szczegóły produktu: {productId}</h2>;
}
function NotFound() { return <h2>404 - Nie znaleziono strony</h2>; }
// Główny komponent aplikacji
function App() {
const navLinkStyles = ({ isActive }) => ({
fontWeight: isActive ? \"bold\" : \"normal\",
marginRight: \"10px\"
});
return (
<div>
<nav style={{ padding: \"10px\", background: \"#eee\" }}>
<NavLink to=\"/\" style={navLinkStyles}>Home</NavLink>
<NavLink to=\"/products\" style={navLinkStyles}>Produkty</NavLink>
</nav>
<hr />
<div style={{ padding: \"10px\" }}>
<Routes>
<Route path=\"/\" element={<Home />} />
<Route path=\"/products\" element={<Products />} />
<Route path=\"/products/:productId\" element={<ProductDetail />} />
<Route path=\"*\" element={<NotFound />} />
</Routes>
</div>
</div>
);
}
export default App;
Cel: Stworzyć prostą aplikację z trzema stronami (Home, Products, ProductDetail) używając React Router.
Kroki:
- Zainstaluj
react-router-dom
. - Owiń komponent
App
w<BrowserRouter>
w plikuindex.js
/main.jsx
. - Stwórz komponenty dla stron:
Home
,Products
,ProductDetail
. - W komponencie
App
stwórz nawigację (użyj<NavLink>
) do strony Home i Products. - W komponencie
App
zdefiniuj trasy za pomocą<Routes>
i<Route>
: /
ma renderowaćHome
./products
ma renderowaćProducts
./products/:productId
ma renderowaćProductDetail
(trasa dynamiczna).- W komponencie
Products
wyświetl listę linków (<Link>
) do kilku przykładowych produktów (np./products/1
,/products/2
). - W komponencie
ProductDetail
użyj HookauseParams
, aby odczytaćproductId
z URL i wyświetlić go. - Dodaj trasę "catch-all" (
path="*"
) renderującą prosty komponentNotFound
.
Zadanie do samodzielnego wykonania
Rozbuduj aplikację z ćwiczenia praktycznego:
- Dodaj nową sekcję "Admin" z zagnieżdżonymi trasami.
- Stwórz komponent
AdminLayout
z własną nawigacją (np. "Użytkownicy", "Ustawienia") i komponentem<Outlet />
. - Zdefiniuj trasę
/admin
, która renderujeAdminLayout
. - Dodaj trasy podrzędne wewnątrz
/admin
, np./admin/users
i/admin/settings
, które renderują odpowiednie komponenty wewnątrz<Outlet />
wAdminLayout
. - Dodaj link do sekcji Admin w głównej nawigacji.
FAQ - React Router - Podstawy Routingu
Jaka jest różnica między `BrowserRouter` a `HashRouter`?
`BrowserRouter` używa HTML5 History API do tworzenia "czystych" URL-i (np. `/users/123`). Wymaga konfiguracji po stronie serwera, aby poprawnie obsługiwać odświeżanie strony na dowolnej ścieżce. `HashRouter` używa hasha w URL (np. `/#/users/123`). Nie wymaga specjalnej konfiguracji serwera, ale URL-e są mniej estetyczne i mogą mieć problemy z SEO. `BrowserRouter` jest zazwyczaj preferowany.
Czy mogę używać zwykłych tagów `<a>` zamiast `<Link>`?
Użycie zwykłego tagu `<a href="...">` spowoduje pełne przeładowanie strony przez przeglądarkę, co niweczy ideę SPA i routingu po stronie klienta. Należy używać komponentu `<Link>` (lub `<NavLink>`), aby React Router mógł przechwycić nawigację i zaktualizować UI bez przeładowania.
Jak przekazać dodatkowe dane (stan) podczas nawigacji?
Komponent `Link` oraz funkcja `navigate` pozwalają na przekazanie stanu za pomocą opcji `state`. Dane te są dostępne w komponencie docelowym za pomocą Hooka `useLocation`. Przykład: `<Link to="/profile" state={{ from: location }} />` lub `navigate("/profile", { state: { userId: 123 } })`. W komponencie docelowym: `const location = useLocation(); const userId = location.state?.userId;`.
Jak ostylować aktywny link za pomocą `NavLink`?
`NavLink` pozwala przekazać funkcję do propa `style` lub `className`. Funkcja ta otrzymuje obiekt z właściwością `isActive` (boolean). Można zwrócić odpowiedni obiekt stylu lub nazwę klasy w zależności od wartości `isActive`. Przykład: `className={({ isActive }) => isActive ? "active-link" : "inactive-link"}`.
Czy mogę mieć wiele zestawów `<Routes>` w aplikacji?
Tak, można używać `<Routes>` w różnych miejscach aplikacji, np. do definiowania tras głównych i zagnieżdżonych tras w layoutach. Każdy `<Routes>` będzie renderował tylko pierwszy pasujący `<Route>` wewnątrz siebie.
Jak obsługiwać parametry zapytania (query parameters) w URL (np. `?sort=asc`)?
React Router v6 udostępnia Hook `useSearchParams` do odczytywania i modyfikowania parametrów zapytania w URL. Zwraca on obiekt `URLSearchParams` i funkcję do jego aktualizacji. Przykład: `const [searchParams, setSearchParams] = useSearchParams(); const sortOrder = searchParams.get("sort"); setSearchParams({ sort: "desc" });`.
Co to jest `Outlet`?
`Outlet` to komponent używany w komponentach tras nadrzędnych (layoutach) do wskazania miejsca, w którym powinny być renderowane komponenty pasujących tras podrzędnych. Działa jak placeholder dla zagnieżdżonej zawartości trasy.