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.
Budując aplikację z pełnym routingiem, musimy zaplanować kilka warstw:
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.
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.
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.
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.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:
/, /login, /missions) są dopasowywane jako pierwszepath="*" musi być ostatnia - przechwytuje wszystko, co nie pasuje do żadnej zdefiniowanej trasyStrona 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.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}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!