Kurs JavaScript i React · Moduł 8: Zaawansowane wzorce React
Code splitting
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:
- Używamy
React.lazydo dynamicznego importowania komponentów - Opakowujemy komponenty w
Suspense, aby pokazać fallback podczas ładowania - 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:
Komponenty migają podczas ładowania
- Rozwiązanie: Zaprojektuj fallback tak, aby pasował do układu końcowego komponentu
Za dużo małych chunków
- Rozwiązanie: Grupuj powiązane komponenty w jednym dynamic import
Problemy z SSR (Server-Side Rendering)
- Rozwiązanie: Użyj
loadable-componentszamiastReact.lazydla SSR
- Rozwiązanie: Użyj
Dobre praktyki w zakresie code splitting
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
Analizuj swoje paczki - regularnie monitoruj rozmiar i zawartość bundli
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));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ść
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()iSuspensedla 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: