Kurs Next.js · Moduł 1: Konfiguracja Next.js
Struktura katalogów i plików w Next.js (app/ vs pages/)
W tej lekcji9
W Metropolii Quantum inżynierowie projektują zaawansowane systemy transportu, które mogą działać zarówno na klasycznych drogach, jak i na nowych szlakach kwantowej teleportacji. Podobnie, Next.js oferuje dwa równoległe systemy routingu: tradycyjny Pages Router i nowoczesny App Router. W tym module szczegółowo przeanalizujemy różnice między tymi dwoma podejściami i zrozumiemy, jak organizować pliki i katalogi w naszych projektach.
Dwie filozofie routingu w Next.js
Next.js pierwotnie korzystał z routingu opartego na plikach w folderze pages/. W Next.js 13 wprowadzono nowy system routingu oparty na folderze app/. Ta ewolucja przypomina przejście od klasycznych systemów transportu do kwantowej teleportacji w Metropolii Quantum - nowa technologia jest bardziej zaawansowana, ale obie nadal współistnieją.
Pages Router: Klasyczne podejście
Pages Router to oryginalny system routingu w Next.js. Jest prosty, dobrze udokumentowany i nadal szeroko używany. W tej konwencji każdy plik JavaScript lub TypeScript w katalogu pages/ automatycznie staje się ścieżką w aplikacji.
1pages/
2├── index.js # Trasa: /
3├── about.js # Trasa: /about
4├── contact.tsx # Trasa: /contact
5├── products/
6│ ├── index.js # Trasa: /products
7│ └── [id].js # Trasa: /products/:id (dynamiczna)
8└── api/
9 └── hello.js # Endpoint API: /api/helloW tym systemie:
- Pliki bezpośrednio mapują się na ścieżki URL
- Dynamiczne segmenty ścieżki są oznaczane nawiasami kwadratowymi
[param] - Specjalny katalog
api/służy do obsługi endpointów API - Każdy plik eksportuje domyślnie komponent React lub funkcję obsługi API
App Router: Nowoczesne podejście
App Router, wprowadzony w Next.js 13 i dopracowany w wersji 15, reprezentuje nowe podejście oparte na React Server Components. Jest to jak przejście od tradycyjnych pojazdów do teleportacji kwantowej - fundamentalna zmiana w sposobie myślenia o aplikacjach webowych.
1app/
2├── layout.tsx # Główny układ (wymagany)
3├── page.tsx # Trasa: /
4├── about/
5│ └── page.tsx # Trasa: /about
6├── blog/
7│ ├── page.tsx # Trasa: /blog
8│ ├── layout.tsx # Układ specyficzny dla /blog i jego podścieżek
9│ └── [slug]/
10│ └── page.tsx # Trasa: /blog/:slug (dynamiczna)
11└── api/
12 └── users/
13 └── route.ts # Endpoint API: /api/usersW App Routerze:
- Tylko katalogi definiują ścieżki, a specjalne pliki wewnątrz tych katalogów pełnią konkretne funkcje
- Plik
page.tsxrenderuje komponent strony dla danej ścieżki - Plik
layout.tsxdefiniuje współdzielony układ dla wszystkich stron w katalogu i jego podkatalogach - Dynamiczne segmenty nadal używają nawiasów kwadratowych:
[param] - Endpointy API są tworzone przy użyciu plików
route.ts
Nazewnictwo plików i konwencje w App Routerze
App Router wprowadza "konwencję ponad konfiguracją" z szeregiem specjalnych plików, które mają określone role. Jest to jak wyspecjalizowane moduły w systemach Metropolii Quantum - każdy z określoną funkcją w ogólnej architekturze.
Podstawowe pliki specjalne:
page.tsx: Renderuje interfejs użytkownika trasy i sprawia, że ścieżka jest publicznie dostępna.
1// app/dashboard/page.tsx 2export default function DashboardPage() { 3 return ( 4 <section> 5 <h1>Panel kontrolny Metropolii Quantum</h1> 6 <p>Witaj w centrum dowodzenia!</p> 7 </section> 8 ); 9}layout.tsx: Definiuje współdzielony interfejs dla segmentu i jego dzieci.
1// app/dashboard/layout.tsx 2export default function DashboardLayout({ 3 children, 4}: { 5 children: React.ReactNode; 6}) { 7 return ( 8 <div className="dashboard-layout"> 9 <nav className="dashboard-sidebar"> 10 {/* Nawigacja panelu bocznego */} 11 </nav> 12 <main className="dashboard-main">{children}</main> 13 </div> 14 ); 15}route.ts: Tworzy endpoint API przy użyciu nowego API obsługi żądań.
1// app/api/status/route.ts 2import { NextResponse } from 'next/server'; 3 4export async function GET() { 5 return NextResponse.json({ 6 status: 'online', 7 systemsOperational: 15, 8 time: new Date().toISOString() 9 }); 10} 11 12export async function POST(request: Request) { 13 const data = await request.json(); 14 // Przetwarzanie danych... 15 return NextResponse.json({ success: true }); 16}loading.tsx: Tworzy interfejs ładowania z React Suspense.
1// app/dashboard/loading.tsx 2export default function DashboardLoading() { 3 return ( 4 <div className="dashboard-loading"> 5 <div className="spinner"></div> 6 <p>Ładowanie danych systemowych Metropolii...</p> 7 </div> 8 ); 9}error.tsx: Tworzy interfejs obsługi błędów z React Error Boundary.
1'use client'; // Komponenty obsługi błędów muszą być komponentami klienckimi 2 3import { useEffect } from 'react'; 4 5export default function DashboardError({ 6 error, 7 reset, 8}: { 9 error: Error; 10 reset: () => void; 11}) { 12 useEffect(() => { 13 // Logowanie błędu do systemu monitorowania 14 console.error(error); 15 }, [error]); 16 17 return ( 18 <div className="error-container"> 19 <h2>Wystąpił błąd w systemach Metropolii!</h2> 20 <p>{error.message}</p> 21 <button onClick={reset}>Spróbuj ponownie</button> 22 </div> 23 ); 24}not-found.tsx: Tworzy interfejs dla nieznalezionego zasobu (404).
1// app/not-found.tsx 2export default function NotFound() { 3 return ( 4 <div className="not-found"> 5 <h2>Strefa nieznana</h2> 6 <p>Nie znaleziono modułu Metropolii Quantum o takim identyfikatorze.</p> 7 <a href="/">Powrót do centrum dowodzenia</a> 8 </div> 9 ); 10}
Zaawansowane funkcje App Routera
App Router wprowadza kilka zaawansowanych funkcji organizacyjnych, które przypominają zaawansowane systemy transportu w Metropolii Quantum - pozwalają na bardziej skomplikowane i elastyczne trasy.
1. Grupy tras (Route Groups)
Grupy tras pozwalają na organizowanie plików bez wpływania na strukturę URL. Są oznaczane przez ujęcie nazwy katalogu w nawiasy: (nazwa-grupy).
1app/
2├── (marketing)/ # Grupa tras (nie wpływa na ścieżkę URL)
3│ ├── about/
4│ │ └── page.tsx # Trasa: /about
5│ └── contact/
6│ └── page.tsx # Trasa: /contact
7└── (dashboard)/ # Inna grupa tras
8 ├── layout.tsx # Współdzielony układ dla tras dashboard
9 ├── page.tsx # Trasa: /
10 └── stats/
11 └── page.tsx # Trasa: /statsGrupy tras są jak dzielnice w Metropolii Quantum - pomagają organizować strukturę miasta, ale nie wpływają na adresy.
2. Trasy równoległe (Parallel Routes)
Trasy równoległe pozwalają na równoczesne renderowanie wielu stron w tym samym widoku, co jest przydatne dla złożonych układów. Są oznaczane przez nazwę slotu ze znakiem "@": @nazwa-slotu.
1app/
2├── layout.tsx # Główny układ
3├── page.tsx # Główna strona
4└── dashboard/
5 ├── layout.tsx # Układ z wieloma slotami
6 │ // layout.tsx definiuje sloty {children, @stats, @notifications}
7 ├── page.tsx # Domyślna zawartość dla slotu głównego
8 ├── @stats/
9 │ ├── page.tsx # Zawartość dla slotu @stats
10 │ └── default.tsx # Wymagany w Next.js 16 (np. zwraca null)
11 └── @notifications/
12 ├── page.tsx # Zawartość dla slotu @notifications
13 └── default.tsx # Wymagany w Next.js 16 (np. zwraca null)W Next.js 16 każdy slot musi mieć plik default.tsx (np. zwracający null albo wywołujący notFound()), inaczej next build zakończy się błędem.
1// app/dashboard/layout.tsx
2export default function DashboardLayout({
3 children,
4 stats,
5 notifications,
6}: {
7 children: React.ReactNode;
8 stats: React.ReactNode;
9 notifications: React.ReactNode;
10}) {
11 return (
12 <div className="dashboard-grid">
13 <main>{children}</main>
14 <aside className="stats-panel">{stats}</aside>
15 <section className="notifications-panel">{notifications}</section>
16 </div>
17 );
18}Trasy równoległe to jak równoległe wymiary w Metropolii Quantum - różne rzeczywistości dostępne jednocześnie.
3. Przechwytujące trasy (Intercepting Routes)
Pozwalają na "przechwycenie" trasy i wyświetlenie jej inaczej, co jest idealne dla modali, które zachowują kontekst nawigacji. Są oznaczane przez nazwę segmentu ze znakiem "(.)": (.)nazwa, (..)nazwa lub (...)nazwa.
1app/
2├── layout.tsx
3├── page.tsx # Trasa: /
4└── dashboard/
5 ├── layout.tsx
6 ├── page.tsx # Trasa: /dashboard
7 └── projects/
8 ├── layout.tsx # Renderuje {children} i {modal}
9 ├── page.tsx # Trasa: /dashboard/projects
10 ├── [id]/
11 │ └── page.tsx # Trasa: /dashboard/projects/:id
12 ├── create/
13 │ └── page.tsx # Trasa: /dashboard/projects/create (pełna strona)
14 └── @modal/
15 ├── default.tsx # Zwraca null, gdy modal jest zamknięty
16 └── (.)create/
17 └── page.tsx # Modal nad listą przy przejściu linkiemPrzy przejściu linkiem z listy Next.js pokaże przechwyconą stronę w slocie @modal nad listą, a po odświeżeniu lub wejściu bezpośrednio z adresu wyświetli pełną stronę create/page.tsx.
Przechwytujące trasy są jak kwantowe skróty w Metropolii Quantum - pozwalają na szybki dostęp do zasobów bez zmiany kontekstu.
Organizacja kodu w Next.js 16
Oprócz struktury routingu Next.js 16 sugeruje określone sposoby organizacji kodu. Jest to jak planowanie dzielnic w Metropolii Quantum - właściwa organizacja zwiększa efektywność i czytelność.
Współdzielone komponenty i zasoby
Komponenty, które są używane w wielu miejscach, powinny być zorganizowane w spójny sposób. Oto zalecana struktura:
1src/
2├── app/ # Aplikacja z App Routerem
3│ ├── layout.tsx
4│ └── page.tsx
5├── components/ # Współdzielone komponenty
6│ ├── ui/ # Podstawowe komponenty UI
7│ │ ├── Button.tsx
8│ │ └── Card.tsx
9│ └── features/ # Komponenty specyficzne dla funkcji
10│ ├── dashboard/
11│ └── auth/
12├── lib/ # Współdzielone funkcje i klasy
13│ ├── api/
14│ └── utils/
15├── hooks/ # Niestandardowe hooki React
16│ ├── useAuth.ts
17│ └── useLocalStorage.ts
18└── styles/ # Style globalne
19 └── globals.cssCo-location vs. Centralizacja
Next.js 16 i App Router wspierają podejście "co-location", gdzie pliki związane z konkretną trasą są umieszczane blisko siebie. Jest to jak mikrodystrykty w Metropolii Quantum - wszystko, czego potrzebujesz, jest blisko.
1app/
2└── dashboard/
3 ├── page.tsx
4 ├── loading.tsx # Ładowanie dla tej trasy
5 ├── error.tsx # Obsługa błędów dla tej trasy
6 ├── layout.tsx # Układ dla tej trasy
7 ├── actions.ts # Server Actions dla tej trasy
8 ├── components/ # Komponenty specyficzne dla tej trasy
9 │ ├── StatusCard.tsx
10 │ └── DashboardNav.tsx
11 └── styles/ # Style specyficzne dla tej trasy
12 └── dashboard.module.cssPorównanie App Routera i Pages Routera
Teraz, gdy poznaliśmy oba systemy routingu, porównajmy je - podobnie jak inżynierowie Metropolii Quantum mogliby porównywać klasyczny transport z teleportacją kwantową.
Renderowanie
Pages Router:
- Domyślnie każda strona jest prerenderowana do HTML (automatyczna optymalizacja statyczna), a w przeglądarce hydratowana
- SSR i SSG dostępne przez getServerSideProps i getStaticProps
- Stosunkowo proste modele danych
App Router:
- Domyślnie Server Components
- Wybór między Server i Client Components
- Bardziej zaawansowany model przepływu danych
- Streaming renderowania
W Pages Routerze dane strony pobiera funkcja eksportowana obok komponentu, np. getStaticProps, którą Next.js wywołuje raz, podczas budowania:
1// pages/posts.js (Pages Router)
2export async function getStaticProps() {
3 const posts = await getPosts();
4 return { props: { posts } };
5}Funkcja getServerSideProps ma tę samą budowę, ale Next.js wywołuje ją przy każdym żądaniu.
Stany ładowania
Pages Router:
- Ręczne zarządzanie stanami ładowania
- Często wykorzystuje zewnętrzne biblioteki
App Router:
- Wbudowane komponenty loading.tsx
- Integracja z React Suspense
- Automatyczne wskaźniki ładowania
Obsługa błędów
Pages Router:
- Ręczne zarządzanie błędami
- Ogólny komponent dla błędów 500
App Router:
- Komponenty error.tsx z granularną obsługą błędów
- Integracja z React Error Boundary
- Możliwość naprawy/ponowienia po błędzie
Układy
Pages Router:
- Ręczne tworzenie układów z komponentem _app.js
- Jeden układ dla całej aplikacji
- Zagnieżdżone układy wymagają dodatkowej pracy
App Router:
- Wbudowany system układów poprzez layout.tsx
- Możliwość tworzenia zagnieżdżonych układów
- Współdzielenie układów jest proste i intuicyjne
Kiedy używać którego systemu routingu?
Wybór między App Routerem a Pages Routerem przypomina wybór między kwantową teleportacją a tradycyjnym transportem w Metropolii Quantum - zależy od konkretnych potrzeb.
Używaj App Routera, gdy:
- Tworzysz nowy projekt od podstaw
- Chcesz korzystać z najnowszych funkcji React i Next.js
- Potrzebujesz zaawansowanych możliwości układu i organizacji
- Zależy Ci na lepszej wydajności i strumieniowym renderowaniu (streamingu)
- Chcesz korzystać z React Server Components
Używaj Pages Routera, gdy:
- Masz istniejący projekt oparty na Pages Routerze
- Potrzebujesz prostszego modelu mentalnego
- Używasz bibliotek, które nie są jeszcze kompatybilne z App Routerem
- Chcesz używać gotowych wzorców i rozwiązań
Migracja z Pages Routera do App Routera
Jeśli masz istniejący projekt używający Pages Routera, możesz stopniowo migrować do App Routera. Jest to jak modernizacja dzielnic w Metropolii Quantum - nie musisz przebudowywać wszystkiego naraz.
Next.js 16 pozwala na współistnienie obu systemów. Możesz zacząć używać app/ dla nowych funkcji, jednocześnie zachowując istniejące strony w pages/.
1.
2├── app/ # Nowe funkcje używające App Router
3│ ├── new-feature/
4│ │ └── page.tsx # /new-feature
5│ └── layout.tsx
6└── pages/ # Istniejące strony używające Pages Router
7 ├── index.js # /
8 └── about.js # /aboutWażne uwagi dotyczące migracji:
- Ścieżki w
app/mają priorytet nad ścieżkami wpages/, ale ten sam adres nie może istnieć w obu katalogach naraz: Next.js zgłosi błąd podczas budowania - Obie struktury używają tej samej konfiguracji Next.js
- Możesz stopniowo przenosić strony z
pages/doapp/
Najlepsze praktyki organizacji plików
Niezależnie od wybranego systemu routingu warto trzymać się pewnych najlepszych praktyk:
Struktura zorientowana na funkcje: Organizuj komponenty i kod wokół funkcji biznesowych, a nie według typów technicznych.
1src/ 2├── features/ 3│ ├── auth/ 4│ │ ├── components/ 5│ │ ├── hooks/ 6│ │ └── utils/ 7│ └── dashboard/ 8│ ├── components/ 9│ ├── hooks/ 10│ └── utils/Modułowe podejście: Traktuj każdą funkcjonalność jako izolowany moduł z jasno określonym API.
Spójna konwencja nazewnictwa: Ustal konwencje i trzymaj się ich w całym projekcie.
- Pliki komponentów: PascalCase (Button.tsx)
- Hooki: camelCase z przedrostkiem "use" (useAuth.ts)
- Pomocnicze funkcje: camelCase (formatDate.ts)
Importy względne vs. absolutne: Używaj aliasów importu (
@/) dla lepszej czytelności i łatwiejszego przenoszenia plików.Kolokacja testów: Umieszczaj testy blisko testowanych komponentów.
1src/ 2├── components/ 3│ ├── Button.tsx 4│ └── Button.test.tsx
Podsumowanie
Struktura katalogów i plików w Next.js 16 jest jak architektura Metropolii Quantum - z odpowiednim planowaniem może być zarówno elegancka, jak i funkcjonalna. App Router wprowadza bardziej zaawansowany, ale także bardziej elastyczny model organizacji aplikacji, podczas gdy Pages Router oferuje prostsze i bardziej bezpośrednie podejście.
Wybór między nimi zależy od specyficznych potrzeb projektu, ale w nowych projektach zaleca się korzystanie z App Routera, aby wykorzystać najnowsze funkcje i optymalizacje. Pamiętaj, że dobra organizacja projektu to klucz do łatwej konserwacji i rozszerzalności w przyszłości.
W kolejnej lekcji zagłębimy się w system komponentów Next.js 16, szczególnie w różnice między Client Components a Server Components, które są fundamentalną częścią App Routera.
Kod do tej lekcji: App.tsx
1// Props i State - Metropolis Quantum
2import React, { useState } from 'react';
3
4console.log("Props i State w React");
5console.log("Zarządzanie danymi w Quantum City\n");
6
7// ==========================================
8// 1. PROPS - Przekazywanie Danych
9// ==========================================
10console.log("=== 1. PROPS - właściwości ===");
11
12interface UserCardProps {
13 name: string;
14 role: string;
15 level: number;
16 avatar: string;
17}
18
19const UserCard: React.FC<UserCardProps> = ({ name, role, level, avatar }) => {
20 return (
21 <div style={{
22 background: 'rgba(255, 255, 255, 0.1)',
23 padding: '20px',
24 borderRadius: '12px',
25 border: '2px solid #64ffda',
26 display: 'flex',
27 alignItems: 'center',
28 gap: '15px',
29 marginBottom: '15px'
30 }}>
31 <div style={{ fontSize: '3rem' }}>{avatar}</div>
32 <div>
33 <h3 style={{ color: '#64ffda', margin: '0 0 5px 0' }}>{name}</h3>
34 <p style={{ margin: '5px 0', color: '#b0bec5' }}>Rola: {role}</p>
35 <p style={{ margin: '5px 0', color: '#ff9800' }}>Poziom: {level}</p>
36 </div>
37 </div>
38 );
39};
40
41// ==========================================
42// 2. STATE - Stan Komponentu
43// ==========================================
44console.log("\n=== 2. STATE - hook useState ===");
45
46function QuantumCounter() {
47 // useState zwraca [wartość, funkcję do zmiany wartości]
48 const [energy, setEnergy] = useState<number>(50);
49 const [isCharging, setIsCharging] = useState<boolean>(false);
50
51 const charge = () => {
52 if (energy < 100) {
53 setEnergy(energy + 10);
54 setIsCharging(true);
55 setTimeout(() => setIsCharging(false), 300);
56 console.log(`Ładowanie energii: ${energy + 10}%`);
57 }
58 };
59
60 const discharge = () => {
61 if (energy > 0) {
62 setEnergy(energy - 10);
63 console.log(`Rozładowanie energii: ${energy - 10}%`);
64 }
65 };
66
67 const reset = () => {
68 setEnergy(50);
69 console.log("Reset energii do 50%");
70 };
71
72 const getEnergyColor = () => {
73 if (energy > 70) return '#4caf50';
74 if (energy > 30) return '#ff9800';
75 return '#f44336';
76 };
77
78 return (
79 <div style={{
80 background: 'rgba(124, 77, 255, 0.2)',
81 padding: '20px',
82 borderRadius: '12px',
83 border: '2px solid #7c4dff'
84 }}>
85 <h3 style={{ color: '#7c4dff' }}>Kwantowy generator energii</h3>
86
87 <div style={{
88 background: 'rgba(0, 0, 0, 0.5)',
89 padding: '15px',
90 borderRadius: '8px',
91 marginBottom: '15px'
92 }}>
93 <div style={{
94 fontSize: '2rem',
95 textAlign: 'center',
96 color: getEnergyColor(),
97 marginBottom: '10px'
98 }}>
99 {energy}%
100 </div>
101 <div style={{
102 width: '100%',
103 height: '20px',
104 background: 'rgba(255, 255, 255, 0.1)',
105 borderRadius: '10px',
106 overflow: 'hidden'
107 }}>
108 <div style={{
109 width: `${energy}%`,
110 height: '100%',
111 background: `linear-gradient(90deg, ${getEnergyColor()}, #64ffda)`,
112 transition: 'all 0.3s ease',
113 transform: isCharging ? 'scale(1.05)' : 'scale(1)'
114 }}></div>
115 </div>
116 </div>
117
118 <div style={{ display: 'flex', gap: '10px', justifyContent: 'center' }}>
119 <button
120 onClick={charge}
121 disabled={energy >= 100}
122 style={{
123 background: energy >= 100 ? '#555' : 'linear-gradient(45deg, #4caf50, #8bc34a)',
124 color: 'white',
125 border: 'none',
126 padding: '10px 20px',
127 borderRadius: '6px',
128 cursor: energy >= 100 ? 'not-allowed' : 'pointer',
129 fontWeight: 'bold'
130 }}
131 >
132 Naładuj
133 </button>
134
135 <button
136 onClick={discharge}
137 disabled={energy <= 0}
138 style={{
139 background: energy <= 0 ? '#555' : 'linear-gradient(45deg, #f44336, #e91e63)',
140 color: 'white',
141 border: 'none',
142 padding: '10px 20px',
143 borderRadius: '6px',
144 cursor: energy <= 0 ? 'not-allowed' : 'pointer',
145 fontWeight: 'bold'
146 }}
147 >
148 Rozładuj
149 </button>
150
151 <button
152 onClick={reset}
153 style={{
154 background: 'linear-gradient(45deg, #607d8b, #90a4ae)',
155 color: 'white',
156 border: 'none',
157 padding: '10px 20px',
158 borderRadius: '6px',
159 cursor: 'pointer',
160 fontWeight: 'bold'
161 }}
162 >
163 Resetuj
164 </button>
165 </div>
166 </div>
167 );
168}
169
170// ==========================================
171// 3. Props i State razem
172// ==========================================
173console.log("\n=== 3. Props i State razem ===");
174
175interface SystemStatusProps {
176 systemName: string;
177 initialStatus: 'online' | 'offline' | 'maintenance';
178}
179
180const SystemStatus: React.FC<SystemStatusProps> = ({ systemName, initialStatus }) => {
181 const [status, setStatus] = useState(initialStatus);
182 const [uptime, setUptime] = useState(0);
183
184 const toggleStatus = () => {
185 const statuses: Array<'online' | 'offline' | 'maintenance'> = ['online', 'offline', 'maintenance'];
186 const currentIndex = statuses.indexOf(status);
187 const nextStatus = statuses[(currentIndex + 1) % statuses.length];
188 setStatus(nextStatus);
189 setUptime(0);
190 console.log(`Status systemu ${systemName} zmieniony na: ${nextStatus}`);
191 };
192
193 const getStatusColor = () => {
194 switch(status) {
195 case 'online': return '#4caf50';
196 case 'offline': return '#f44336';
197 case 'maintenance': return '#ff9800';
198 }
199 };
200
201 return (
202 <div style={{
203 background: 'rgba(255, 255, 255, 0.1)',
204 padding: '15px',
205 borderRadius: '10px',
206 border: `2px solid ${getStatusColor()}`,
207 marginBottom: '15px'
208 }}>
209 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
210 <div>
211 <h4 style={{ margin: '0 0 10px 0', color: '#64ffda' }}>{systemName}</h4>
212 <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
213 <div style={{
214 width: '12px',
215 height: '12px',
216 borderRadius: '50%',
217 background: getStatusColor(),
218 boxShadow: `0 0 10px ${getStatusColor()}`
219 }}></div>
220 <span style={{ color: getStatusColor(), textTransform: 'uppercase', fontWeight: 'bold' }}>
221 {status}
222 </span>
223 </div>
224 </div>
225 <button
226 onClick={toggleStatus}
227 style={{
228 background: 'linear-gradient(45deg, #2196f3, #21cbf3)',
229 color: 'white',
230 border: 'none',
231 padding: '8px 16px',
232 borderRadius: '6px',
233 cursor: 'pointer',
234 fontWeight: 'bold'
235 }}
236 >
237 Zmień status
238 </button>
239 </div>
240 </div>
241 );
242};
243
244// ==========================================
245// Główna aplikacja
246// ==========================================
247function App() {
248 const citizens = [
249 { name: 'Alex Quantum', role: 'Główny architekt', level: 42, avatar: '' },
250 { name: 'Nova Star', role: 'Analityk danych', level: 38, avatar: '' },
251 { name: 'Cypher Neo', role: 'Ekspert ds. bezpieczeństwa', level: 45, avatar: '' }
252 ];
253
254 return (
255 <div style={{
256 padding: '20px',
257 background: 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%)',
258 minHeight: '100vh',
259 color: 'white'
260 }}>
261 <h1 style={{ textAlign: 'center', color: '#64ffda' }}>Panel sterowania Metropolii</h1>
262
263 <div style={{ marginTop: '30px' }}>
264 <h2 style={{ color: '#7c4dff' }}>Mieszkańcy (demo Props)</h2>
265 {citizens.map((citizen, index) => (
266 <UserCard
267 key={index}
268 name={citizen.name}
269 role={citizen.role}
270 level={citizen.level}
271 avatar={citizen.avatar}
272 />
273 ))}
274 </div>
275
276 <div style={{ marginTop: '30px' }}>
277 <h2 style={{ color: '#7c4dff' }}>System energii (demo State)</h2>
278 <QuantumCounter />
279 </div>
280
281 <div style={{ marginTop: '30px' }}>
282 <h2 style={{ color: '#7c4dff' }}>Status systemów (Props + State)</h2>
283 <SystemStatus systemName="Quantum Core" initialStatus="online" />
284 <SystemStatus systemName="Neural Network" initialStatus="maintenance" />
285 <SystemStatus systemName="Transport Grid" initialStatus="offline" />
286 </div>
287
288 <div style={{
289 marginTop: '30px',
290 background: 'rgba(100, 255, 218, 0.1)',
291 padding: '20px',
292 borderRadius: '10px',
293 borderLeft: '4px solid #64ffda'
294 }}>
295 <h3>Props a State:</h3>
296 <ul style={{ lineHeight: '1.8' }}>
297 <li><strong>Props</strong>: Przekazywane z rodzica, tylko do odczytu, jak parametry funkcji</li>
298 <li><strong>State</strong>: Wewnętrzny stan komponentu, można modyfikować, re-renderuje komponent</li>
299 <li><strong>Props + State</strong>: Najczęściej używane razem w dynamicznych interfejsach</li>
300 </ul>
301 </div>
302 </div>
303 );
304}
305
306export 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. Co oznacza 'deklaratywne programowanie' w kontekście React?
2. Komponenty React mogą być:
Zadania praktyczne w grze
- Układanie w poziomie
Ułóż ścieżkę do dynamicznej strony posta w Next.js
- Edytor kodu
Utwórz komponent strony About dla pliku pages/about.js (export default function About). Strona ma wyświetlać nagłówek <h1> lub <h2> z nazwą firmy i akapit <p> z informacjami o firmie.