Kurs JavaScript i React · Moduł 6: React Router
Kompletna architektura routingu - Centrum Dowodzenia Nawigacją
W tej lekcji9
Nawigatorze, dotarliśmy do ostatniego etapu naszej podróży przez Pas Asteroid! Czas zebrać wszystkie techniki nawigacyjne, które poznaliśmy, i zbudować kompletny system routingu - prawdziwe Centrum Dowodzenia Nawigacją dla naszej kosmicznej floty.
W tej lekcji zobaczymy, jak wszystkie elementy React Router współpracują ze sobą w jednej, spójnej aplikacji.
Architektura kompletnej aplikacji z routingiem
Budując aplikację z pełnym routingiem, musimy zaplanować kilka warstw:
- Warstwa uwierzytelniania - AuthContext + ProtectedRoute
- Warstwa nawigacji - NavLink z aktywnymi stylami
- Warstwa tras - zagnieżdżone Routes z Outlet
- Warstwa parametrów - dynamiczne ścieżki z useParams
- Warstwa przekierowań - obsługa 404 i zabezpieczeń
Struktura folderów
W większych aplikacjach warto zorganizować pliki związane z routingiem w logiczną strukturę:
1src/
2├── App.jsx // Główny komponent z BrowserRouter
3├── routes/
4│ ├── AppRoutes.jsx // Definicja wszystkich tras
5│ └── ProtectedRoute.jsx
6├── context/
7│ └── AuthContext.jsx // Kontekst uwierzytelniania
8├── layouts/
9│ ├── MainLayout.jsx // Layout z nawigacją i Outlet
10│ └── DashboardLayout.jsx
11├── pages/
12│ ├── HomePage.jsx
13│ ├── LoginPage.jsx
14│ ├── MissionDetail.jsx
15│ └── NotFound.jsx
16└── components/
17 └── Navigation.jsx // NavLink menuTaka organizacja pozwala łatwo zarządzać nawet rozbudowanymi aplikacjami z dziesiątkami tras.
Łączenie AuthContext z routingiem
Centralnym elementem jest AuthProvider owijający całą aplikację:
1function AuthProvider({ children }) {
2 const [user, setUser] = useState(null);
3
4 const login = (name) => setUser({ name });
5 const logout = () => setUser(null);
6
7 return (
8 <AuthContext.Provider value={{ user, login, logout }}>
9 {children}
10 </AuthContext.Provider>
11 );
12}
13
14// W App.jsx:
15function App() {
16 return (
17 <AuthProvider>
18 <BrowserRouter>
19 <Navigation />
20 <Routes>
21 {/* trasy publiczne i chronione */}
22 </Routes>
23 </BrowserRouter>
24 </AuthProvider>
25 );
26}AuthProvider musi być na zewnątrz BrowserRouter, aby kontekst uwierzytelniania był dostępny we wszystkich komponentach tras.
Nawigacja z NavLink i warunkowym renderowaniem
Komponent nawigacji łączy NavLink (z aktywnymi stylami) z AuthContext (warunkowe wyświetlanie opcji):
1function Navigation() {
2 const { user, logout } = useAuth();
3
4 return (
5 <nav>
6 <NavLink to="/" className={({isActive}) => isActive ? 'active' : ''}>
7 Home
8 </NavLink>
9 <NavLink to="/missions" className={({isActive}) => isActive ? 'active' : ''}>
10 Missions
11 </NavLink>
12
13 {user ? (
14 <>
15 <NavLink to="/dashboard" className={({isActive}) => isActive ? 'active' : ''}>
16 Dashboard
17 </NavLink>
18 <span>Zalogowany: {user.name}</span>
19 <button onClick={logout}>Wyloguj</button>
20 </>
21 ) : (
22 <NavLink to="/login">Logowanie</NavLink>
23 )}
24 </nav>
25 );
26}Dzięki temu nawigacja automatycznie reaguje na stan uwierzytelniania - zalogowany użytkownik widzi Dashboard i przycisk wylogowania, niezalogowany widzi link do logowania.
Zagnieżdżone trasy z layoutem
Sekcja misji wykorzystuje zagnieżdżone trasy z Outlet:
1function MissionsLayout() {
2 return (
3 <div className="missions-layout">
4 <aside>
5 <h3>Misje</h3>
6 <NavLink to="/missions/apollo">Apollo</NavLink>
7 <NavLink to="/missions/artemis">Artemis</NavLink>
8 <NavLink to="/missions/voyager">Voyager</NavLink>
9 </aside>
10 <main>
11 <Outlet />
12 </main>
13 </div>
14 );
15}
16
17// W konfiguracji tras:
18<Route path="/missions" element={<MissionsLayout />}>
19 <Route index element={<MissionsList />} />
20 <Route path=":missionId" element={<MissionDetail />} />
21</Route>Index route (<Route index />) renderuje domyślną zawartość (listę misji), gdy użytkownik jest na /missions. Dynamiczny parametr :missionId obsługuje szczegóły konkretnej misji.
Kompletna konfiguracja tras
Oto jak wygląda pełna konfiguracja tras łącząca wszystkie poznane techniki:
1function App() {
2 return (
3 <AuthProvider>
4 <BrowserRouter>
5 <Navigation />
6 <Routes>
7 {/* Strona główna */}
8 <Route path="/" element={<HomePage />} />
9
10 {/* Logowanie */}
11 <Route path="/login" element={<LoginPage />} />
12
13 {/* Zagnieżdżone trasy z layoutem */}
14 <Route path="/missions" element={<MissionsLayout />}>
15 <Route index element={<MissionsList />} />
16 <Route path=":missionId" element={<MissionDetail />} />
17 </Route>
18
19 {/* Chroniona trasa */}
20 <Route path="/dashboard" element={
21 <ProtectedRoute>
22 <DashboardPage />
23 </ProtectedRoute>
24 } />
25
26 {/* Obsługa 404 */}
27 <Route path="*" element={<NotFound />} />
28 </Routes>
29 </BrowserRouter>
30 </AuthProvider>
31 );
32}Kolejność elementów ma znaczenie:
- Konkretne ścieżki (
/,/login,/missions) są dopasowywane jako pierwsze - Trasa
path="*"musi być ostatnia - przechwytuje wszystko, co nie pasuje do żadnej zdefiniowanej trasy
Logowanie z zapamiętaniem ścieżki
Strona logowania wykorzystuje useNavigate i useLocation do przekierowania użytkownika z powrotem na stronę, z której został przekierowany:
1function LoginPage() {
2 const [name, setName] = useState('');
3 const { login } = useAuth();
4 const navigate = useNavigate();
5 const location = useLocation();
6
7 // Ścieżka, z której użytkownik został przekierowany
8 const from = location.state?.from?.pathname || '/dashboard';
9
10 const handleSubmit = (e) => {
11 e.preventDefault();
12 if (name.trim()) {
13 login(name);
14 navigate(from, { replace: true }); // replace: true usuwa /login z historii
15 }
16 };
17
18 return (
19 <form onSubmit={handleSubmit}>
20 <input value={name} onChange={(e) => setName(e.target.value)} />
21 <button type="submit">Zaloguj</button>
22 </form>
23 );
24}Opcja replace: true w navigate jest kluczowa - dzięki niej użytkownik po zalogowaniu nie wróci na stronę logowania po kliknięciu "Wstecz" w przeglądarce.
Obsługa strony 404
Strona Not Found zawiera link powrotny do strony głównej:
1function NotFound() {
2 return (
3 <div className="not-found">
4 <h1>404</h1>
5 <p>Strona nie została odnaleziona</p>
6 <Link to="/">Wróć na stronę główną</Link>
7 </div>
8 );
9}Podsumowanie architektury
Kompletna aplikacja z routingiem w React łączy następujące elementy:
| Element | Rola | Hook / Komponent |
|---|---|---|
| Kontener tras | Włącza routing | BrowserRouter |
| Definicja tras | Mapuje URL na komponenty | Routes, Route |
| Nawigacja | Linki z aktywnymi stylami | NavLink |
| Zagnieżdżanie | Współdzielony layout | Outlet, Route children |
| Parametry dynamiczne | Szczegóły elementów | useParams |
| Nawigacja programowa | Przekierowanie po akcji | useNavigate |
| Stan lokalizacji | Przekazywanie ukrytych danych | useLocation |
| Ochrona tras | Wymuszanie logowania | ProtectedRoute + Navigate |
| Obsługa błędów | Strony nieistniejące | Route path="*" |
| Kontekst auth | Globalne dane użytkownika | createContext + useContext |
Wypróbuj kompletną aplikację w edytorze poniżej - zaloguj się, przejdź między misjami, sprawdź Dashboard i przetestuj stronę 404!
Kod do tej lekcji: App.jsx
1import React, { useState, createContext, useContext, Suspense, lazy } from 'react';
2import {
3 BrowserRouter, Routes, Route, Link, NavLink,
4 useParams, useNavigate, useLocation, Navigate, Outlet
5} from 'react-router-dom';
6
7// Auth Context
8const AuthContext = createContext();
9function useAuth() { return useContext(AuthContext); }
10
11function AuthProvider({ children }) {
12 const [user, setUser] = useState(null);
13 const login = (name) => setUser({ name });
14 const logout = () => setUser(null);
15 return (
16 <AuthContext.Provider value={{ user, login, logout }}>
17 {children}
18 </AuthContext.Provider>
19 );
20}
21
22// Protected Route
23function ProtectedRoute({ children }) {
24 const { user } = useAuth();
25 const location = useLocation();
26 if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
27 return children;
28}
29
30// Components
31function Navigation() {
32 const { user, logout } = useAuth();
33 return (
34 <nav>
35 <div className="nav-brand">SpaceApp</div>
36 <div className="nav-links">
37 <NavLink to="/" className={({isActive}) => isActive ? 'active' : ''}>Home</NavLink>
38 <NavLink to="/missions" className={({isActive}) => isActive ? 'active' : ''}>Missions</NavLink>
39 {user ? (
40 <>
41 <NavLink to="/dashboard" className={({isActive}) => isActive ? 'active' : ''}>Dashboard</NavLink>
42 <span className="user-badge">{user.name}</span>
43 <button onClick={logout}>Logout</button>
44 </>
45 ) : (
46 <NavLink to="/login" className={({isActive}) => isActive ? 'active' : ''}>Login</NavLink>
47 )}
48 </div>
49 </nav>
50 );
51}
52
53function HomePage() {
54 return (
55 <div className="page home">
56 <h1>Welcome to SpaceApp</h1>
57 <p>Your gateway to space exploration</p>
58 <div className="features">
59 <div className="feature">Track Missions</div>
60 <div className="feature">View Analytics</div>
61 <div className="feature">Manage Crew</div>
62 </div>
63 </div>
64 );
65}
66
67function LoginPage() {
68 const [name, setName] = useState('');
69 const { login } = useAuth();
70 const navigate = useNavigate();
71 const location = useLocation();
72 const from = location.state?.from?.pathname || '/dashboard';
73
74 const handleSubmit = (e) => {
75 e.preventDefault();
76 if (name.trim()) {
77 login(name);
78 navigate(from, { replace: true });
79 }
80 };
81
82 return (
83 <div className="page login">
84 <h1>Login</h1>
85 <form onSubmit={handleSubmit}>
86 <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Username" />
87 <button type="submit">Login</button>
88 </form>
89 </div>
90 );
91}
92
93function MissionsLayout() {
94 return (
95 <div className="missions-layout">
96 <aside>
97 <h3>Missions</h3>
98 <NavLink to="/missions/apollo">Apollo</NavLink>
99 <NavLink to="/missions/artemis">Artemis</NavLink>
100 <NavLink to="/missions/voyager">Voyager</NavLink>
101 </aside>
102 <main><Outlet /></main>
103 </div>
104 );
105}
106
107function MissionsList() {
108 return <div className="content"><h2>Select a mission</h2></div>;
109}
110
111function MissionDetail() {
112 const { missionId } = useParams();
113 return (
114 <div className="content">
115 <h2>Mission: {missionId?.toUpperCase()}</h2>
116 <p>Details for {missionId} mission</p>
117 </div>
118 );
119}
120
121function DashboardPage() {
122 const { user } = useAuth();
123 return (
124 <div className="page dashboard">
125 <h1>Dashboard</h1>
126 <p>Welcome back, {user?.name}!</p>
127 <div className="stats">
128 <div className="stat"><span>Missions</span>12</div>
129 <div className="stat"><span>Crew</span>48</div>
130 <div className="stat"><span>Ships</span>8</div>
131 </div>
132 </div>
133 );
134}
135
136function NotFound() {
137 return (
138 <div className="page not-found">
139 <h1>404</h1>
140 <p>Page not found</p>
141 <Link to="/">Go Home</Link>
142 </div>
143 );
144}
145
146function App() {
147 return (
148 <AuthProvider>
149 <BrowserRouter>
150 <div className="app">
151 <Navigation />
152 <Routes>
153 <Route path="/" element={<HomePage />} />
154 <Route path="/login" element={<LoginPage />} />
155 <Route path="/missions" element={<MissionsLayout />}>
156 <Route index element={<MissionsList />} />
157 <Route path=":missionId" element={<MissionDetail />} />
158 </Route>
159 <Route path="/dashboard" element={
160 <ProtectedRoute><DashboardPage /></ProtectedRoute>
161 } />
162 <Route path="*" element={<NotFound />} />
163 </Routes>
164 </div>
165 </BrowserRouter>
166 </AuthProvider>
167 );
168}
169
170export default App;Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jak zaimplementować Protected Route w React Router?
2. Gdzie w komponencie nadrzędnym umieszczamy <Outlet />, aby renderować zagnieżdżone trasy?
To 2 z 7 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Zaimplementuj obsługę błędów 404 z Route path='*' i komponentem NotFound. Dodaj nawigację z linkami do istniejących i nieistniejących stron, aby przetestować obsługę błędów.
- Klikanie w kolejności
Uporządkuj programową nawigację z state:
- Układanie w pionie
Uporządkuj kroki programatycznej nawigacji za pomocą useNavigate:
- Układanie w pionie
Uporządkuj kroki przekazywania i odczytywania danych między trasami:
- Układanie w pionie
Uporządkuj kroki implementacji Protected Route z komponentem Navigate:
- Klikanie w kolejności
Uporządkuj zarządzanie search params:
- Układanie w pionie
Uporządkuj kroki nawigacji w React Router od kliknięcia linku do wyświetlenia strony:
- Układanie w pionie
Uporządkuj kroki obsługi nieautoryzowanego dostępu do chronionej trasy:
- Układanie w poziomie
Ułóż elementy trasy obsługującej stronę 404:
- Układanie w poziomie
Ułóż składnię Route z dynamicznym parametrem id:
- Edytor kodu
Zbuduj kompleksowy Navigation System zawierający: React Router setup z BrowserRouter, nested routes dla struktury /fleet/:id/missions/:missionId, dynamic routing z params, Link i NavLink components z active styles, useNavigate dla programmatic navigation, useParams i useLocation hooks, protected routes z authentication, lazy loading z React.lazy i Suspense, search params z useSearchParams, oraz 404 error handling. Stwórz system nawigacyjny godny najbardziej zaawansowanej floty przemierzającej Pas Asteroid!