Kurs JavaScript i React · Moduł 8: Zaawansowane wzorce React

Code splitting

10 min czytania
W tej lekcji14

W miarę jak nasza kosmiczna aplikacja React rośnie, plik JavaScript, który ładuje się w przeglądarce, również się powiększa. Może to prowadzić do dłuższych czasów ładowania strony, większego zużycia pamięci i ogólnie słabszej wydajności. Code splitting (podział kodu) to technika, która pozwala na "podział" aplikacji na mniejsze części, które mogą być ładowane dynamicznie, tylko wtedy, gdy są potrzebne.

Czym jest code splitting?

Code splitting to technika, która dzieli duży pakiet kodu JavaScript na mniejsze kawałki (tzw. "chunki"), które mogą być ładowane na żądanie lub równolegle. Zamiast wysyłać całą aplikację do użytkownika od razu, przesyłamy tylko to, co jest potrzebne do początkowego renderowania, a reszta jest ładowana w tle lub gdy jest potrzebna.

Jest to jak start kosmicznej misji - nie wysyłamy wszystkich zasobów od razu, ale etapami: najpierw statek główny, potem moduły dodatkowe, a na końcu mniejsze narzędzia i wyposażenie, gdy stają się potrzebne.

Dlaczego potrzebujemy code splitting?

Wyobraźmy sobie, że tworzymy kosmiczną aplikację z wieloma modułami:

  • Panel kontrolny misji
  • System monitorowania astronautów
  • Mapa galaktyki
  • Encyklopedia odkrytych planet
  • Symulator lotu
  • Centrum komunikacji międzygwiezdnej

Jeśli zbudujemy całą aplikację jako jeden pakiet, użytkownik musi pobrać cały ten kod, nawet jeśli chce tylko sprawdzić mapę galaktyki. W rezultacie:

  • Dłuższy czas ładowania pierwszej strony
  • Większe zużycie pamięci przeglądarki
  • Większe koszty transferu danych (szczególnie istotne na urządzeniach mobilnych)
  • Niepotrzebne ładowanie i parsowanie kodu, który nie jest używany

Korzyści z code splitting

  • Szybsze początkowe ładowanie - krótszy czas do interaktywności (TTI - Time To Interactive)
  • Lepsza wydajność na urządzeniach mobilnych - mniejsze zużycie pamięci i procesora
  • Priorytetyzacja krytycznych ścieżek - najważniejsze części aplikacji ładują się pierwsze
  • Lepsza pamięć podręczna - mniejsze części łatwiej się buforują i aktualizują
  • Lepsze doświadczenie użytkownika - aplikacja jest responsywna szybciej

Implementacja code splitting w React

React wspiera code splitting za pomocą dynamicznego importu i komponentu React.lazy. Zobaczmy, jak to działa:

1. Dynamiczny import

Funkcja import() to standardowa funkcja ES, która pozwala na dynamiczne importowanie modułów. Zamiast ładować wszystkie zależności na górze pliku, możemy załadować je tylko wtedy, gdy są potrzebne:

1// Bez code splitting
2import { calculateOrbit } from './complexOrbitCalculations';
3
4function OrbitCalculator({ planetData }) {
5  const orbitInfo = calculateOrbit(planetData);
6  return <div>{/* Renderowanie wyników */}</div>;
7}
8
9// Z code splitting
10function OrbitCalculator({ planetData }) {
11  const [orbitInfo, setOrbitInfo] = useState(null);
12  const [loading, setLoading] = useState(true);
13
14  useEffect(() => {
15    // Dynamiczny import modułu dopiero, gdy komponent się zamontuje
16    import('./complexOrbitCalculations')
17      .then(module => {
18        const result = module.calculateOrbit(planetData);
19        setOrbitInfo(result);
20        setLoading(false);
21      });
22  }, [planetData]);
23
24  if (loading) return <p>Obliczanie orbity...</p>;
25  return <div>{/* Renderowanie wyników */}</div>;
26}

2. React.lazy

React.lazy to funkcja, która pozwala na renderowanie dynamicznego importu jako zwykłego komponentu. Działa tylko z domyślnymi eksportami (default exports).

1import React, { Suspense } from 'react';
2
3// Zwykły import
4// import GalaxyMap from './GalaxyMap';
5
6// Leniwy import
7const GalaxyMap = React.lazy(() => import('./GalaxyMap'));
8const PlanetDetails = React.lazy(() => import('./PlanetDetails'));
9const SpaceshipControls = React.lazy(() => import('./SpaceshipControls'));
10
11function SpaceExplorer() {
12  const [view, setView] = useState('map');
13
14  return (
15    <div className="space-explorer">
16      <nav>
17        <button onClick={() => setView('map')}>Mapa Galaktyki</button>
18        <button onClick={() => setView('planets')}>Szczegóły Planet</button>
19        <button onClick={() => setView('controls')}>Panel Sterowania</button>
20      </nav>
21
22      <Suspense fallback={<div>Ładowanie modułu...</div>}>
23        {view === 'map' && <GalaxyMap />}
24        {view === 'planets' && <PlanetDetails />}
25        {view === 'controls' && <SpaceshipControls />}
26      </Suspense>
27    </div>
28  );
29}

W powyższym przykładzie:

  1. Używamy React.lazy do dynamicznego importowania komponentów
  2. Opakowujemy komponenty w Suspense, aby pokazać fallback podczas ładowania
  3. Komponenty są ładowane tylko wtedy, gdy są potrzebne (gdy użytkownik zmienia widok)

Suspense

Suspense to komponent React, który pozwala na "zawieszenie" renderowania do momentu, aż pewne warunki zostają spełnione (np. dane są załadowane lub kod komponenty jest dostępny).

Suspense przyjmuje prop fallback, który jest renderowany podczas oczekiwania:

1<Suspense fallback={<LoadingSpinner message="Przygotowywanie modułu..." />}>
2  <LazilyLoadedComponent />
3</Suspense>

Suspense może obsłużyć wiele komponentów Lazy na raz:

1<Suspense fallback={<LoadingSpinner />}>
2  <LazyComponent1 />
3  <div>
4    <LazyComponent2 />
5  </div>
6  <LazyComponent3 />
7</Suspense>

Zagnieżdżanie Suspense

Możemy również zagnieżdżać komponenty Suspense, aby uzyskać bardziej granularną kontrolę nad tym, co jest wyświetlane podczas ładowania:

1function SpaceStation() {
2  return (
3    <div>
4      <h1>Stacja Kosmiczna Alpha</h1>
5
6      {/* Główny układ ładuje się natychmiast */}
7      <StationLayout />
8
9      {/* Mapa ładuje się z własnym fallbackiem */}
10      <Suspense fallback={<p>Wczytywanie mapy stacji...</p>}>
11        <StationMap />
12      </Suspense>
13
14      {/* Panel kontrolny ma zagnieżdżone komponenty lazy */}
15      <Suspense fallback={<p>Przygotowywanie panelu kontrolnego...</p>}>
16        <ControlPanel>
17          <Suspense fallback={<p>Ładowanie systemów podtrzymywania życia...</p>}>
18            <LifeSupportMonitor />
19          </Suspense>
20
21          <Suspense fallback={<p>Ładowanie kontroli silników...</p>}>
22            <EngineControls />
23          </Suspense>
24        </ControlPanel>
25      </Suspense>
26    </div>
27  );
28}

Code splitting w oparciu o routing

Jednym z najczęstszych sposobów zastosowania code splitting jest podział kodu na podstawie tras (routes). Każda strona lub widok staje się oddzielnym chunkiem kodu:

1import React, { Suspense, lazy } from 'react';
2import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
3import Navbar from './Navbar';
4import LoadingPage from './LoadingPage';
5
6// Leniwe ładowanie stron
7const MissionControl = lazy(() => import('./pages/MissionControl'));
8const GalaxyMap = lazy(() => import('./pages/GalaxyMap'));
9const CrewManagement = lazy(() => import('./pages/CrewManagement'));
10const ResearchDatabase = lazy(() => import('./pages/ResearchDatabase'));
11const SpaceshipConfig = lazy(() => import('./pages/SpaceshipConfig'));
12
13function App() {
14  return (
15    <Router>
16      <Navbar />
17      <Suspense fallback={<LoadingPage />}>
18        <Switch>
19          <Route exact path="/" component={MissionControl} />
20          <Route path="/galaxy-map" component={GalaxyMap} />
21          <Route path="/crew" component={CrewManagement} />
22          <Route path="/research" component={ResearchDatabase} />
23          <Route path="/spaceship" component={SpaceshipConfig} />
24        </Switch>
25      </Suspense>
26    </Router>
27  );
28}

Code splitting bibliotek

Możemy również stosować code splitting dla dużych bibliotek, które nie są potrzebne natychmiast:

1import React, { useState } from 'react';
2
3function DataVisualizer({ data }) {
4  const [charts, setCharts] = useState(null);
5  const [showCharts, setShowCharts] = useState(false);
6
7  const loadChartsLibrary = async () => {
8    // Załaduj bibliotekę dopiero, gdy użytkownik chce zobaczyć wykresy
9    const D3Module = await import('d3');
10    const ChartModule = await import('./ChartComponent');
11
12    setCharts({
13      d3: D3Module.default,
14      ChartComponent: ChartModule.default
15    });
16  };
17
18  const handleShowCharts = () => {
19    if (!charts) {
20      setShowCharts(true);
21      loadChartsLibrary();
22    } else {
23      setShowCharts(true);
24    }
25  };
26
27  return (
28    <div className="data-visualizer">
29      <h2>Dane eksploracji kosmicznej</h2>
30      <button onClick={handleShowCharts}>
31        {showCharts ? 'Ukryj wykresy' : 'Pokaż wykresy'}
32      </button>
33
34      {showCharts && (
35        <>
36          {charts ? (
37            <charts.ChartComponent data={data} d3={charts.d3} />
38          ) : (
39            <p>Ładowanie zaawansowanych wykresów...</p>
40          )}
41        </>
42      )}
43    </div>
44  );
45}

Preloading

Czasami wiemy, że komponent będzie potrzebny wkrótce (np. gdy użytkownik najedzie na przycisk, który pokaże modal). Możemy zacząć ładować moduł wcześniej:

1const PlanetExplorer = React.lazy(() => import('./PlanetExplorer'));
2
3function PlanetInfoButton({ planetId }) {
4  const [showExplorer, setShowExplorer] = useState(false);
5
6  const handleMouseEnter = () => {
7    // Rozpocznij pobieranie modułu, gdy użytkownik najedzie na przycisk
8    const PlanetExplorerModule = import('./PlanetExplorer');
9  };
10
11  return (
12    <>
13      <button
14        onMouseEnter={handleMouseEnter}
15        onClick={() => setShowExplorer(true)}
16      >
17        Eksploruj planetę {planetId}
18      </button>
19
20      {showExplorer && (
21        <Suspense fallback={<p>Przygotowywanie eksploratora...</p>}>
22          <PlanetExplorer planetId={planetId} />
23        </Suspense>
24      )}
25    </>
26  );
27}

Named exports i code splitting

React.lazy działa tylko z domyślnymi eksportami (default exports). Jeśli chcemy użyć nazwanych eksportów (named exports), musimy utworzyć pośredni moduł:

1// MathUtils.js
2export const calculateDistance = (a, b) => { /* ... */ };
3export const calculateVelocity = (distance, time) => { /* ... */ };
4export const calculateAcceleration = (velocity, time) => { /* ... */ };
5
6// DistanceCalculator.js (moduł pośredni)
7export { calculateDistance } from './MathUtils';
8export default calculateDistance;
9
10// Użycie
11const DistanceCalculator = React.lazy(() => import('./DistanceCalculator'));

Alternatywnie, możemy ręcznie obsłużyć import i wyodrębnić żądany eksport:

1const calculateVelocity = React.lazy(() =>
2  import('./MathUtils').then(module => ({ default: module.calculateVelocity }))
3);

Strategie dzielenia kodu

1. Podział według tras (Routes)

Najczęstszy i najłatwiejszy sposób - każda strona to oddzielny chunk kodu.

2. Podział według funkcjonalności (Feature-based)

Komponenty związane z określoną funkcjonalnością (np. panel administracyjny, zaawansowana analityka) są grupowane razem.

1// Ładowanie całego modułu administratora na żądanie
2const AdminDashboard = React.lazy(() => import('./admin/Dashboard'));
3
4function App() {
5  const [isAdmin, setIsAdmin] = useState(false);
6
7  // isAdmin jest ustawiane po logowaniu użytkownika
8
9  return (
10    <div>
11      {isAdmin ? (
12        <Suspense fallback={<p>Ładowanie panelu admina...</p>}>
13          <AdminDashboard />
14        </Suspense>
15      ) : (
16        <UserDashboard />
17      )}
18    </div>
19  );
20}

3. Podział według priorytetu (Priority-based)

Krytyczne komponenty ładowane od razu, mniej ważne leniwie.

1// Krytyczne komponenty importowane standardowo
2import CriticalSystemStatus from './CriticalSystemStatus';
3import NavigationControls from './NavigationControls';
4
5// Mniej istotne komponenty ładowane leniwie
6const SystemLogs = React.lazy(() => import('./SystemLogs'));
7const CrewSchedule = React.lazy(() => import('./CrewSchedule'));
8const MaintenanceStatus = React.lazy(() => import('./MaintenanceStatus'));
9
10function SpaceshipDashboard() {
11  return (
12    <div className="dashboard">
13      <div className="critical-section">
14        <CriticalSystemStatus />
15        <NavigationControls />
16      </div>
17
18      <Suspense fallback={<p>Ładowanie dodatkowych modułów...</p>}>
19        <div className="secondary-section">
20          <SystemLogs />
21          <CrewSchedule />
22          <MaintenanceStatus />
23        </div>
24      </Suspense>
25    </div>
26  );
27}

4. Podział przy użyciu bibliotek (Vendor splitting)

Dzielenie dużych bibliotek zewnętrznych na oddzielne chunki.

W webpack można to osiągnąć za pomocą konfiguracji splitChunks:

1module.exports = {
2  //...
3  optimization: {
4    splitChunks: {
5      chunks: 'all',
6      maxInitialRequests: Infinity,
7      minSize: 0,
8      cacheGroups: {
9        vendor: {
10          test: /[\/]node_modules[\/]/,
11          name(module) {
12            // get the name. E.g. node_modules/packageName/sub/path
13            // or node_modules/packageName
14            const packageName = module.context.match(
15              /[\/]node_modules[\/](.*?)([\/]|$)/
16            )[1];
17
18            // npm package names are URL-safe, but some servers don't like @ symbols
19            return `npm.${packageName.replace('@', '')}`;
20          },
21        },
22      },
23    },
24  },
25};

Analiza i debugowanie code splitting

Analizowanie wielkości paczek

Narzędzia takie jak "webpack-bundle-analyzer" lub "source-map-explorer" pomagają wizualizować zawartość paczek i identyfikować możliwości podziału:

1# Instalacja
2npm install --save-dev webpack-bundle-analyzer
3
4# Konfiguracja w webpack.config.js
5const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
6
7module.exports = {
8  plugins: [
9    new BundleAnalyzerPlugin()
10  ]
11}

Debugowanie problemów z code-splitting

Częste problemy z code splitting i ich rozwiązania:

  1. Komponenty migają podczas ładowania

    • Rozwiązanie: Zaprojektuj fallback tak, aby pasował do układu końcowego komponentu
  2. Za dużo małych chunków

    • Rozwiązanie: Grupuj powiązane komponenty w jednym dynamic import
  3. Problemy z SSR (Server-Side Rendering)

    • Rozwiązanie: Użyj loadable-components zamiast React.lazy dla SSR

Dobre praktyki w zakresie code splitting

  1. Nie przesadzaj z podziałem - zbyt wiele małych chunków może prowadzić do problemów z wydajnością ze względu na liczne żądania HTTP

  2. Analizuj swoje paczki - regularnie monitoruj rozmiar i zawartość bundli

  3. Używaj Magic Comments w webpack do lepszej kontroli nad chunkami:

    1// Nazwany chunk
    2const HomeComponent = React.lazy(() => import(
    3  /* webpackChunkName: "home" */
    4  './HomeComponent'
    5));
    6
    7// Prefetch - zasugeruj przeglądarce, że ten zasób będzie potrzebny
    8const SettingsComponent = React.lazy(() => import(
    9  /* webpackChunkName: "settings" */
    10  /* webpackPrefetch: true */
    11  './SettingsComponent'
    12));
    13
    14// Preload - załaduj ten zasób z wysokim priorytetem
    15const ProfileComponent = React.lazy(() => import(
    16  /* webpackChunkName: "profile" */
    17  /* webpackPreload: true */
    18  './ProfileComponent'
    19));
  4. Używaj code splitting w połączeniu z pamięcią podręczną - prawidłowo skonfigurowane nagłówki cache dla chunków mogą znacznie poprawić wydajność

  5. Monitoruj wpływ na wydajność - używaj narzędzi takich jak Lighthouse, aby mierzyć czasy ładowania i TTI (Time To Interactive)

Code splitting a TypeScript

TypeScript działa dobrze z code splitting, ale wymaga kilku dodatkowych kroków:

1// Upewnij się, że w tsconfig.json jest ustawione:
2// "module": "esnext"
3
4// Dynamiczny import z typowaniem
5const DataVisualization = React.lazy(() =>
6  import('./DataVisualization') as Promise<{ default: React.ComponentType<DataVisProps> }>
7);
8
9interface DataVisProps {
10  data: Array<DataPoint>;
11  width: number;
12  height: number;
13}
14
15interface DataPoint {
16  x: number;
17  y: number;
18  label: string;
19}
20
21function Dashboard() {
22  const [data, setData] = useState<Array<DataPoint>>([]);
23
24  return (
25    <div>
26      <h1>Panel misji kosmicznej</h1>
27
28      <Suspense fallback={<div>Ładowanie wizualizacji...</div>}>
29        <DataVisualization
30          data={data}
31          width={800}
32          height={400}
33        />
34      </Suspense>
35    </div>
36  );
37}

Podsumowanie

Code splitting to potężna technika optymalizacji, która pozwala na znaczne polepszenie wydajności aplikacji React, szczególnie dla większych projektów. Dzięki podziałowi kodu użytkownicy ładują tylko to, czego potrzebują, co prowadzi do szybszego uruchomienia aplikacji i lepszego doświadczenia użytkownika.

Kluczowe narzędzia i metody to:

  • React.lazy() i Suspense dla komponentów
  • Dynamiczny import() dla modułów
  • Strategie podziału oparte na trasach, funkcjonalności lub priorytecie
  • Narzędzia analizy bundli pomagające w podejmowaniu decyzji

Wdrażając code splitting w naszej kosmicznej aplikacji, możemy zapewnić, że użytkownicy szybko uzyskają dostęp do najważniejszych funkcji, a dodatkowe zasoby będą ładowane wtedy, gdy będą potrzebne - podobnie jak w wieloetapowej misji kosmicznej, gdzie każdy moduł jest dostarczany we właściwym czasie.

Kod do tej lekcji: App.jsx
1import React, { createContext, useContext, useState } from 'react';
2
3// ============================================
4// PATTERN: Compound Components
5// ============================================
6// Komponenty współdzielące stan przez Context
7
8// Context dla Accordion
9const AccordionContext = createContext();
10
11function useAccordion() {
12  const context = useContext(AccordionContext);
13  if (!context) {
14    throw new Error('Accordion components must be used within <Accordion>');
15  }
16  return context;
17}
18
19// Główny komponent Accordion
20function Accordion({ children, defaultOpen = null }) {
21  const [openItem, setOpenItem] = useState(defaultOpen);
22
23  const toggle = (id) => {
24    setOpenItem(prev => prev === id ? null : id);
25  };
26
27  return (
28    <AccordionContext.Provider value={{ openItem, toggle }}>
29      <div style={styles.accordion}>
30        {children}
31      </div>
32    </AccordionContext.Provider>
33  );
34}
35
36// Sub-komponent: Item
37function AccordionItem({ children, id }) {
38  const { openItem } = useAccordion();
39  const isOpen = openItem === id;
40
41  return (
42    <div style={{
43      ...styles.item,
44      borderColor: isOpen ? '#00d4ff' : 'rgba(255,255,255,0.1)',
45    }}>
46      {React.Children.map(children, child =>
47        React.cloneElement(child, { itemId: id, isOpen })
48      )}
49    </div>
50  );
51}
52
53// Sub-komponent: Header
54function AccordionHeader({ children, itemId }) {
55  const { openItem, toggle } = useAccordion();
56  const isOpen = openItem === itemId;
57
58  return (
59    <button
60      onClick={() => toggle(itemId)}
61      style={styles.header}
62    >
63      <span>{children}</span>
64      <span style={{
65        transform: isOpen ? 'rotate(180deg)' : 'rotate(0)',
66        transition: 'transform 0.3s ease',
67      }}>
68        ▼
69      </span>
70    </button>
71  );
72}
73
74// Sub-komponent: Panel
75function AccordionPanel({ children, isOpen }) {
76  return (
77    <div style={{
78      ...styles.panel,
79      maxHeight: isOpen ? '500px' : '0',
80      padding: isOpen ? '20px' : '0 20px',
81      opacity: isOpen ? 1 : 0,
82    }}>
83      {children}
84    </div>
85  );
86}
87
88// Łączymy komponenty
89Accordion.Item = AccordionItem;
90Accordion.Header = AccordionHeader;
91Accordion.Panel = AccordionPanel;
92
93// ============================================
94// PATTERN: Tabs Compound Component
95// ============================================
96
97const TabsContext = createContext();
98
99function Tabs({ children, defaultTab }) {
100  const [activeTab, setActiveTab] = useState(defaultTab);
101
102  return (
103    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
104      <div style={styles.tabs}>
105        {children}
106      </div>
107    </TabsContext.Provider>
108  );
109}
110
111function TabList({ children }) {
112  return (
113    <div style={styles.tabList}>
114      {children}
115    </div>
116  );
117}
118
119function Tab({ children, id }) {
120  const { activeTab, setActiveTab } = useContext(TabsContext);
121  const isActive = activeTab === id;
122
123  return (
124    <button
125      onClick={() => setActiveTab(id)}
126      style={{
127        ...styles.tab,
128        background: isActive ? '#00d4ff' : 'transparent',
129        color: isActive ? '#000' : '#fff',
130      }}
131    >
132      {children}
133    </button>
134  );
135}
136
137function TabPanels({ children }) {
138  return <div>{children}</div>;
139}
140
141function TabPanel({ children, id }) {
142  const { activeTab } = useContext(TabsContext);
143
144  if (activeTab !== id) return null;
145
146  return (
147    <div style={styles.tabPanel}>
148      {children}
149    </div>
150  );
151}
152
153Tabs.List = TabList;
154Tabs.Tab = Tab;
155Tabs.Panels = TabPanels;
156Tabs.Panel = TabPanel;
157
158// ============================================
159// Demo App
160// ============================================
161
162function CompoundComponentsDemo() {
163  return (
164    <div style={styles.container}>
165      <header style={styles.headerSection}>
166        <h1 style={styles.title}>Compound Components</h1>
167        <p style={styles.subtitle}>
168          Wzorzec projektowy dla komponentów współdzielących stan
169        </p>
170      </header>
171
172      <div style={styles.content}>
173        {/* Accordion Demo */}
174        <div style={styles.section}>
175          <h2 style={styles.sectionTitle}>Accordion</h2>
176
177          <Accordion defaultOpen="item1">
178            <Accordion.Item id="item1">
179              <Accordion.Header>Czym są Compound Components?</Accordion.Header>
180              <Accordion.Panel>
181                <p>
182                  Compound Components to wzorzec, w którym komponenty współdzielą
183                  stan wewnętrzny poprzez Context. Pozwala to na tworzenie
184                  elastycznych i czytelnych API komponentów.
185                </p>
186              </Accordion.Panel>
187            </Accordion.Item>
188
189            <Accordion.Item id="item2">
190              <Accordion.Header>Zalety wzorca</Accordion.Header>
191              <Accordion.Panel>
192                <ul style={{ marginLeft: '20px' }}>
193                  <li>Czytelna i deklaratywna składnia</li>
194                  <li>Elastyczność w układzie komponentów</li>
195                  <li>Enkapsulacja logiki w rodzicu</li>
196                  <li>Łatwe dodawanie nowych funkcjonalności</li>
197                </ul>
198              </Accordion.Panel>
199            </Accordion.Item>
200
201            <Accordion.Item id="item3">
202              <Accordion.Header>Implementacja</Accordion.Header>
203              <Accordion.Panel>
204                <p>
205                  Implementacja opiera się na React Context do współdzielenia
206                  stanu oraz na static properties dla grupowania sub-komponentów.
207                </p>
208              </Accordion.Panel>
209            </Accordion.Item>
210          </Accordion>
211        </div>
212
213        {/* Tabs Demo */}
214        <div style={styles.section}>
215          <h2 style={styles.sectionTitle}>Tabs</h2>
216
217          <Tabs defaultTab="tab1">
218            <Tabs.List>
219              <Tabs.Tab id="tab1">Planety</Tabs.Tab>
220              <Tabs.Tab id="tab2">Gwiazdy</Tabs.Tab>
221              <Tabs.Tab id="tab3">Galaktyki</Tabs.Tab>
222            </Tabs.List>
223
224            <Tabs.Panels>
225              <Tabs.Panel id="tab1">
226                <h3>Planety Układu Słonecznego</h3>
227                <p>Merkury, Wenus, Ziemia, Mars, Jowisz, Saturn, Uran, Neptun</p>
228              </Tabs.Panel>
229              <Tabs.Panel id="tab2">
230                <h3>Typy gwiazd</h3>
231                <p>Gwiazdy dzielą się na karły, olbrzymy, nadolbrzymy i inne typy spektralne.</p>
232              </Tabs.Panel>
233              <Tabs.Panel id="tab3">
234                <h3>Rodzaje galaktyk</h3>
235                <p>Galaktyki spiralne, eliptyczne, nieregularne i soczewkowate.</p>
236              </Tabs.Panel>
237            </Tabs.Panels>
238          </Tabs>
239        </div>
240      </div>
241
242      <footer style={styles.footer}>
243        <p>Compound Components • Context API • Static Properties</p>
244      </footer>
245    </div>
246  );
247}
248
249const styles = {
250  container: {
251    minHeight: '100vh',
252    background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)',
253    padding: '30px',
254    fontFamily: "'Segoe UI', sans-serif",
255    color: '#fff',
256  },
257  headerSection: {
258    textAlign: 'center',
259    marginBottom: '40px',
260  },
261  title: {
262    fontSize: '2.5em',
263    color: '#00d4ff',
264    marginBottom: '10px',
265  },
266  subtitle: {
267    opacity: 0.7,
268  },
269  content: {
270    maxWidth: '800px',
271    margin: '0 auto',
272  },
273  section: {
274    background: 'rgba(255,255,255,0.05)',
275    borderRadius: '15px',
276    padding: '25px',
277    marginBottom: '25px',
278    border: '1px solid rgba(255,255,255,0.1)',
279  },
280  sectionTitle: {
281    color: '#ffd700',
282    marginBottom: '20px',
283  },
284  accordion: {
285    display: 'flex',
286    flexDirection: 'column',
287    gap: '10px',
288  },
289  item: {
290    borderRadius: '10px',
291    border: '2px solid',
292    overflow: 'hidden',
293    transition: 'border-color 0.3s ease',
294  },
295  header: {
296    width: '100%',
297    padding: '15px 20px',
298    background: 'rgba(255,255,255,0.05)',
299    border: 'none',
300    color: '#fff',
301    fontSize: '16px',
302    cursor: 'pointer',
303    display: 'flex',
304    justifyContent: 'space-between',
305    alignItems: 'center',
306  },
307  panel: {
308    overflow: 'hidden',
309    transition: 'all 0.3s ease',
310    lineHeight: 1.6,
311  },
312  tabs: {},
313  tabList: {
314    display: 'flex',
315    gap: '10px',
316    marginBottom: '20px',
317  },
318  tab: {
319    padding: '12px 24px',
320    border: '2px solid #00d4ff',
321    borderRadius: '8px',
322    cursor: 'pointer',
323    fontWeight: 'bold',
324    transition: 'all 0.3s ease',
325  },
326  tabPanel: {
327    padding: '20px',
328    background: 'rgba(255,255,255,0.05)',
329    borderRadius: '10px',
330  },
331  footer: {
332    textAlign: 'center',
333    marginTop: '40px',
334    opacity: 0.7,
335  },
336};
337
338export default CompoundComponentsDemo;

Widzisz błąd w tej lekcji?

Zadania praktyczne w grze

  • Klikanie w kolejności

    Ułóż składnię React.lazy z dynamicznym importem:

Przydatne artykuły