Kurs Next.js · Moduł 1: Konfiguracja Next.js

Struktura katalogów i plików w Next.js (app/ vs pages/)

11 min czytania
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/hello

W 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/users

W App Routerze:

  • Tylko katalogi definiują ścieżki, a specjalne pliki wewnątrz tych katalogów pełnią konkretne funkcje
  • Plik page.tsx renderuje komponent strony dla danej ścieżki
  • Plik layout.tsx definiuje 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:

  1. 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}
  2. 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}
  3. 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}
  4. 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}
  5. 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}
  6. 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: /stats

Grupy 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 linkiem

Przy 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.css

Co-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.css

Poró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

  1. 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
  2. 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

  1. Pages Router:

    • Ręczne zarządzanie stanami ładowania
    • Często wykorzystuje zewnętrzne biblioteki
  2. App Router:

    • Wbudowane komponenty loading.tsx
    • Integracja z React Suspense
    • Automatyczne wskaźniki ładowania

Obsługa błędów

  1. Pages Router:

    • Ręczne zarządzanie błędami
    • Ogólny komponent dla błędów 500
  2. 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

  1. 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
  2. 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        # /about

Ważne uwagi dotyczące migracji:

  • Ścieżki w app/ mają priorytet nad ścieżkami w pages/, 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/ do app/

Najlepsze praktyki organizacji plików

Niezależnie od wybranego systemu routingu warto trzymać się pewnych najlepszych praktyk:

  1. 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/
  2. Modułowe podejście: Traktuj każdą funkcjonalność jako izolowany moduł z jasno określonym API.

  3. 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)
  4. Importy względne vs. absolutne: Używaj aliasów importu (@/) dla lepszej czytelności i łatwiejszego przenoszenia plików.

  5. 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. 1. Co oznacza 'deklaratywne programowanie' w kontekście React?

  2. 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.

Przydatne artykuły