Kurs JavaScript i React · Moduł 8: Zaawansowane wzorce React
Profilowanie wydajności
W tej lekcji7
Nasza kosmiczna podróż przez React zbliża się do końca, ale zanim wyruszymy na dalsze eksploracje, musimy upewnić się, że nasz statek kosmiczny działa na maksymalnych obrotach. W tym module skupimy się na profilowaniu wydajności aplikacji React - czyli identyfikacji i analizie "wąskich gardeł" w naszym kodzie.
Czym jest profilowanie wydajności?
Profilowanie wydajności to proces identyfikacji miejsc w aplikacji, które zużywają najwięcej zasobów (czas procesora, pamięć) i mogą prowadzić do wolnego działania interfejsu użytkownika. Jest to jak diagnozowanie silników statku kosmicznego, aby sprawdzić, które części nie działają wydajnie i mogą zostać zoptymalizowane.
W kontekście aplikacji React, profilowanie pozwala na:
- Identyfikację komponentów, które renderują się zbyt często
- Zlokalizowanie długo trwających operacji
- Zrozumienie wzorców renderowania
- Wykrycie niepotrzebnych aktualizacji stanu
- Wskazanie miejsc wymagających optymalizacji
Narzędzia do profilowania React
1. React DevTools Profiler
React DevTools to rozszerzenie do przeglądarki, które pozwala na analizę wydajności aplikacji React. Zawiera ono zakładkę "Profiler", która jest najprostszym i najpotężniejszym narzędziem do profilowania aplikacji React.
Jak używać React DevTools Profiler:
Instalacja React DevTools
- Dla Chrome: React Developer Tools
- Dla Firefox: React Developer Tools
Uruchomienie profilowania:
- Otwórz narzędzia deweloperskie w przeglądarce
- Przejdź do zakładki "Profiler" w React DevTools
- Kliknij przycisk nagrywania (czerwony kółko)
- Wykonaj akcje w aplikacji, które chcesz profilować
- Zatrzymaj nagrywanie
Analiza wyników:
- Flame Chart (Wykres płomienia) - pokazuje hierarchię renderowanych komponentów i czas ich renderowania
- Ranked Chart (Wykres rankingowy) - sortuje komponenty według czasu renderowania
- Component Chart (Wykres komponentów) - pokazuje, które komponenty zostały przerenderowane w każdej aktualizacji
Interpretacja kolorów w Profilerze:
- Szary - komponent nie renderował się podczas aktualizacji
- Żółty/Pomarańczowy/Czerwony - komponent renderował się, a kolor wskazuje, jak długo to trwało (im bardziej czerwony, tym dłużej)
- Czarny - komponent był przyczyną aktualizacji (np. przez zmianę stanu)
2. Chrome Performance Panel
Chrome DevTools zawiera panel Performance, który pozwala na bardziej ogólne profilowanie wydajności strony:
Uruchomienie profilowania:
- Otwórz DevTools w Chrome (F12)
- Przejdź do zakładki "Performance"
- Kliknij przycisk nagrywania
- Wykonaj akcje w aplikacji
- Zatrzymaj nagrywanie
Analizuj wyniki:
- Main - pokazuje aktywność w głównym wątku (gdzie dzieje się większość pracy JS)
- Frames - pokazuje ilość klatek na sekundę (FPS)
- Network - pokazuje aktywność sieciową
- Memory - pokazuje zużycie pamięci
Ten panel jest szczególnie przydatny do identyfikacji:
- Długo działających skryptów blokujących główny wątek
- Nadmiernych operacji na DOM
- Problemów z wydajnością renderowania
3. Wbudowane API profilowania w React
React dostarcza wbudowane API do profilowania w trybie deweloperskim:
1// Importujemy komponent Profiler
2import { Profiler } from 'react';
3
4// Funkcja callback wywoływana po renderowaniu
5function onRenderCallback(
6 id, // identyfikator profilowanego drzewa komponentów
7 phase, // "mount" (pierwsze renderowanie) lub "update" (przerenderowanie)
8 actualDuration, // czas spędzony na renderowaniu
9 baseDuration, // szacowany czas renderowania całego poddrzewa bez optymalizacji
10 startTime, // kiedy React rozpoczął renderowanie
11 commitTime, // kiedy React zakończył renderowanie
12 interactions // zestaw "interakcji" śledzonych dla tego renderowania
13) {
14 console.log({
15 id,
16 phase,
17 actualDuration,
18 baseDuration,
19 startTime,
20 commitTime
21 });
22}
23
24// Użycie komponentu Profiler w aplikacji
25function App() {
26 return (
27 <Profiler id="MissionControl" onRender={onRenderCallback}>
28 <MissionControl />
29 </Profiler>
30 );
31}Możesz zagnieżdżać komponenty Profiler, aby profilować różne części aplikacji:
1function Dashboard() {
2 return (
3 <Profiler id="Dashboard" onRender={onRenderCallback}>
4 <h1>Panel kontrolny misji</h1>
5
6 <Profiler id="Navigation" onRender={onRenderCallback}>
7 <Navigation />
8 </Profiler>
9
10 <Profiler id="Stats" onRender={onRenderCallback}>
11 <MissionStats />
12 </Profiler>
13
14 <Profiler id="Crew" onRender={onRenderCallback}>
15 <CrewStatus />
16 </Profiler>
17 </Profiler>
18 );
19}Ten sposób profilowania jest szczególnie przydatny, gdy:
- Chcesz zbierać dane wydajnościowe w środowisku testowym
- Masz specyficzne komponenty, które chcesz monitorować
- Chcesz zautomatyzować zbieranie danych wydajnościowych
Typowe problemy wydajnościowe i ich profilowanie
1. Zbyt częste renderowanie komponentów
Problem: Komponenty renderują się ponownie, nawet gdy ich dane nie zmieniły się.
Jak profilować:
- Użyj React DevTools Profiler, aby zobaczyć, które komponenty renderują się zbyt często
- Sprawdź, czy komponenty rodzice nie powodują niepotrzebnych renderowań potomków
Przykład problemu:
1function ParentComponent() {
2 const [count, setCount] = useState(0);
3
4 // Ta funkcja jest tworzona na nowo przy każdym renderowaniu
5 const handleClick = () => {
6 console.log('Button clicked');
7 };
8
9 return (
10 <div>
11 <button onClick={() => setCount(count + 1)}>
12 Zwiększ licznik: {count}
13 </button>
14
15 {/* ChildComponent będzie renderowany ponownie przy każdej zmianie count,
16 nawet jeśli handleClick ma taką samą funkcjonalność */}
17 <ChildComponent onClick={handleClick} />
18 </div>
19 );
20}Rozwiązanie:
1function ParentComponent() {
2 const [count, setCount] = useState(0);
3
4 // Memoizacja funkcji zapobiega tworzeniu nowej referencji przy każdym renderowaniu
5 const handleClick = useCallback(() => {
6 console.log('Button clicked');
7 }, []);
8
9 return (
10 <div>
11 <button onClick={() => setCount(count + 1)}>
12 Zwiększ licznik: {count}
13 </button>
14
15 {/* ChildComponent owinięty w React.memo nie renderuje się ponownie,
16 gdy jego propsy nie zmieniają się */}
17 <MemoizedChildComponent onClick={handleClick} />
18 </div>
19 );
20}
21
22const MemoizedChildComponent = React.memo(ChildComponent);2. Ciężkie obliczenia w renderowaniu
Problem: Wykonywanie skomplikowanych obliczeń podczas renderowania powoduje opóźnienia w UI.
Jak profilować:
- Obserwuj w React DevTools Profiler komponenty, które mają najdłuższy czas renderowania
- Użyj Chrome Performance Panel, aby zobaczyć długie zadania JavaScript
Przykład problemu:
1function DataAnalytics({ data }) {
2 // Ciężkie obliczenia wykonywane przy każdym renderowaniu
3 const processedData = processLargeDataset(data);
4 const statistics = calculateStatistics(processedData);
5
6 return (
7 <div>
8 <h2>Analiza danych misji</h2>
9 <DataVisualisation stats={statistics} />
10 </div>
11 );
12}Rozwiązanie:
1function DataAnalytics({ data }) {
2 // Memoizacja ciężkich obliczeń - wykonują się tylko gdy data się zmienia
3 const processedData = useMemo(() => {
4 return processLargeDataset(data);
5 }, [data]);
6
7 const statistics = useMemo(() => {
8 return calculateStatistics(processedData);
9 }, [processedData]);
10
11 return (
12 <div>
13 <h2>Analiza danych misji</h2>
14 <DataVisualisation stats={statistics} />
15 </div>
16 );
17}3. Nadmierne aktualizacje stanu
Problem: Zbyt częste aktualizacje stanu powodują kaskadowe rerenderowania.
Jak profilować:
- W React DevTools Profiler znajdź komponenty oznaczone na czarno (inicjatorzy aktualizacji)
- Obserwuj wzorce aktualizacji - czy niektóre komponenty aktualizują się w grupach?
Przykład problemu:
1function SpaceshipSensors() {
2 const [temperature, setTemperature] = useState(0);
3 const [pressure, setPressure] = useState(0);
4 const [oxygen, setOxygen] = useState(0);
5 const [radiation, setRadiation] = useState(0);
6
7 // Pobieranie danych z czujników co 100ms
8 useEffect(() => {
9 const interval = setInterval(() => {
10 // Każda aktualizacja powoduje osobne renderowanie
11 setTemperature(getTemperature());
12 setPressure(getPressure());
13 setOxygen(getOxygen());
14 setRadiation(getRadiation());
15 }, 100);
16
17 return () => clearInterval(interval);
18 }, []);
19
20 return (
21 <div>
22 <h2>Odczyty czujników</h2>
23 <p>Temperatura: {temperature}°C</p>
24 <p>Ciśnienie: {pressure} hPa</p>
25 <p>Poziom tlenu: {oxygen}%</p>
26 <p>Poziom radiacji: {radiation} μSv/h</p>
27 </div>
28 );
29}Rozwiązanie:
1function SpaceshipSensors() {
2 // Przechowywanie powiązanych danych razem w jednym obiekcie stanu
3 const [sensorData, setSensorData] = useState({
4 temperature: 0,
5 pressure: 0,
6 oxygen: 0,
7 radiation: 0
8 });
9
10 useEffect(() => {
11 const interval = setInterval(() => {
12 // Jedna aktualizacja stanu zamiast czterech
13 setSensorData({
14 temperature: getTemperature(),
15 pressure: getPressure(),
16 oxygen: getOxygen(),
17 radiation: getRadiation()
18 });
19 }, 100);
20
21 return () => clearInterval(interval);
22 }, []);
23
24 return (
25 <div>
26 <h2>Odczyty czujników</h2>
27 <p>Temperatura: {sensorData.temperature}°C</p>
28 <p>Ciśnienie: {sensorData.pressure} hPa</p>
29 <p>Poziom tlenu: {sensorData.oxygen}%</p>
30 <p>Poziom radiacji: {sensorData.radiation} μSv/h</p>
31 </div>
32 );
33}4. Duże listy i wizualizacje danych
Problem: Renderowanie długich list lub skomplikowanych wizualizacji może spowalniać interfejs.
Jak profilować:
- Użyj React DevTools Profiler, aby zidentyfikować komponenty list, które długo się renderują
- Sprawdź, jak często renderują się poszczególne elementy listy
Przykład użycia React DevTools do profilowania wydajności listy:
- Uruchom profilowanie
- Przewiń listę, aby zobaczyć, jak reaguje aplikacja
- Zatrzymaj profilowanie
- Sprawdź, które komponenty mają najdłuższy czas renderowania w wykresie rankingowym
Rozwiązanie:
- Wirtualizacja list (React Window, React Virtualized)
- Memoizacja elementów listy
- Paginacja zamiast wyświetlania wszystkich elementów naraz
Zaawansowane techniki profilowania
1. User Timing API
Browser Performance API pozwala na tworzenie własnych znaczników czasowych:
1function ExpensiveComponent({ data }) {
2 useEffect(() => {
3 // Początek pomiaru
4 performance.mark('expensive-calculation-start');
5
6 // Wykonanie kosztownych obliczeń
7 const result = performExpensiveCalculation(data);
8
9 // Koniec pomiaru
10 performance.mark('expensive-calculation-end');
11
12 // Utworzenie pomiaru między znacznikami
13 performance.measure(
14 'expensive-calculation',
15 'expensive-calculation-start',
16 'expensive-calculation-end'
17 );
18
19 // Logowanie wyników
20 console.log(performance.getEntriesByName('expensive-calculation'));
21 }, [data]);
22
23 //
24}Te znaczniki będą widoczne w panelu Performance w Chrome DevTools.
2. Monitorowanie wydajności w środowisku produkcyjnym
Dla monitorowania wydajności w produkcji można użyć:
- Web Vitals - biblioteka Google do pomiaru kluczowych wskaźników wydajności
- New Relic, Datadog, Sentry - narzędzia APM (Application Performance Monitoring)
- Własne rozwiązania korzystające z Performance API i Profiler API
Przykład zbierania danych z React Profiler i wysyłania ich na serwer:
1function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
2 // Zbieramy tylko dane z fazy commit i trwające dłużej niż 10ms
3 if (phase === 'update' && actualDuration > 10) {
4 sendToAnalyticsServer({
5 componentId: id,
6 renderTime: actualDuration,
7 timestamp: Date.now()
8 });
9 }
10}
11
12function App() {
13 return (
14 <Profiler id="App" onRender={onRenderCallback}>
15 <AppContent />
16 </Profiler>
17 );
18}Studium przypadku: Profilowanie i optymalizacja aplikacji React
Przeanalizujmy proces profilowania i optymalizacji wydajności aplikacji React na przykładzie panelu kontrolnego misji kosmicznej:
Krok 1: Identyfikacja problemu
Użytkownicy zgłaszają, że panel kontrolny działa wolno, szczególnie podczas wyświetlania danych telemetrycznych i listy zdarzeń.
Krok 2: Profilowanie aplikacji
Używamy React DevTools Profiler, aby zidentyfikować źródło problemu:
- Nagrywamy sesję podczas normalnego użytkowania panelu
- Analizujemy wyniki w Profilerze
- Odkrywamy, że:
- Komponent
TelemetryChartrenderuje się bardzo długo (>100ms) - Komponent
EventsListrenderuje się za każdym razem, gdy przychodzą nowe dane telemetryczne - Funkcja
calculateTrendszajmuje dużo czasu procesora
- Komponent
Krok 3: Optymalizacja
Na podstawie wyników profilowania wprowadzamy następujące optymalizacje:
- Optymalizacja TelemetryChart:
1// Przed optymalizacją
2function TelemetryChart({ data }) {
3 // Ciężkie przetwarzanie danych przy każdym renderowaniu
4 const chartData = processChartData(data);
5
6 return <LineChart data={chartData} />;
7}
8
9// Po optymalizacji
10const TelemetryChart = React.memo(function TelemetryChart({ data }) {
11 // Memoizacja przetwarzania danych
12 const chartData = useMemo(() => processChartData(data), [data]);
13
14 return <LineChart data={chartData} />;
15});- Optymalizacja EventsList:
1// Przed optymalizacją
2function EventsList({ events }) {
3 return (
4 <div>
5 {events.map(event => (
6 <EventItem key={event.id} event={event} />
7 ))}
8 </div>
9 );
10}
11
12// Po optymalizacji
13const EventsList = React.memo(function EventsList({ events }) {
14 return (
15 <div>
16 {events.map(event => (
17 <MemoizedEventItem key={event.id} event={event} />
18 ))}
19 </div>
20 );
21});
22
23const MemoizedEventItem = React.memo(EventItem);- Przeniesienie ciężkich obliczeń do web workera:
1// Przed optymalizacją
2function MissionControlPanel({ telemetryData }) {
3 const trends = calculateTrends(telemetryData); // Blokuje główny wątek
4
5 return (
6 <div>
7 <TrendsDisplay trends={trends} />
8 {/* inne komponenty */}
9 </div>
10 );
11}
12
13// Po optymalizacji
14function MissionControlPanel({ telemetryData }) {
15 const [trends, setTrends] = useState(null);
16
17 useEffect(() => {
18 if (!telemetryData) return;
19
20 // Utworzenie web workera
21 const worker = new Worker('./trendsWorker.js');
22
23 // Nasłuchiwanie odpowiedzi
24 worker.addEventListener('message', (event) => {
25 setTrends(event.data);
26 worker.terminate();
27 });
28
29 // Wysłanie danych do przetworzenia
30 worker.postMessage(telemetryData);
31 }, [telemetryData]);
32
33 return (
34 <div>
35 {trends ? <TrendsDisplay trends={trends} /> : <LoadingSpinner />}
36 {/* inne komponenty */}
37 </div>
38 );
39}Krok 4: Weryfikacja optymalizacji
Po wprowadzeniu optymalizacji ponownie profilujemy aplikację:
- Czas renderowania
TelemetryChartspadł z >100ms do ~15ms - Komponent
EventsListnie renderuje się już przy aktualizacjach danych telemetrycznych - Główny wątek nie jest już blokowany przez
calculateTrends
Dzięki tym optymalizacjom interfejs użytkownika działa płynnie, a panel kontrolny reaguje natychmiast na interakcje użytkownika.
Najlepsze praktyki profilowania
Profiluj w środowisku podobnym do produkcyjnego
- Uruchamiaj aplikację w trybie produkcyjnym (
npm run buildi serwuj zbudowane pliki) - Testuj na urządzeniach podobnych do tych używanych przez użytkowników
- Uruchamiaj aplikację w trybie produkcyjnym (
Ustal punkty odniesienia
- Zmierz wydajność przed i po optymalizacjach
- Określ konkretne cele wydajnościowe (np. czas pierwszego renderowania < 2s)
Profiluj regularnie
- Włącz profilowanie do cyklu rozwoju aplikacji
- Monitoruj wydajność po wprowadzeniu większych zmian
Koncentruj się na percepcji użytkownika
- Priorytetyzuj optymalizacje, które mają największy wpływ na odczuwalną wydajność
- Pamiętaj o kluczowych metrykach Web Vitals (LCP, FID, CLS)
Dokumentuj wyniki profilowania
- Zapisuj dane wydajnościowe przed i po optymalizacjach
- Udostępniaj wiedzę i techniki optymalizacji zespołowi
Podsumowanie
Profilowanie wydajności jest niezbędnym elementem tworzenia wysokiej jakości aplikacji React. Podobnie jak inżynierowie kosmiczni monitorują każdy aspekt działania statku, tak i my powinniśmy stale analizować wydajność naszych aplikacji, aby zapewnić użytkownikom płynne i responsywne doświadczenie.
Najważniejsze punkty do zapamiętania:
- Używaj odpowiednich narzędzi - React DevTools Profiler, Chrome Performance Panel
- Identyfikuj wąskie gardła - znajdź komponenty, które renderują się zbyt często lub zbyt długo
- Stosuj odpowiednie techniki optymalizacji - memoizacja, unikanie zbędnych renderowań, wirtualizacja list
- Mierz skuteczność optymalizacji - porównuj wydajność przed i po zmianach
- Optymalizuj z umiarem - pamiętaj, że przedwczesna optymalizacja może prowadzić do nadmiernie skomplikowanego kodu
Dzięki tym technikom i narzędziom profilowania, będziesz w stanie tworzyć wydajne aplikacje React, które zapewnią doskonałe doświadczenie użytkownikom, nawet w najbardziej skomplikowanych interfejsach kosmicznego panelu kontrolnego.
Kod do tej lekcji: App.jsx
1import React, { useState, useMemo, useCallback, useEffect, useRef, Profiler } from 'react';
2
3// ============================================
4// PROFILOWANIE WYDAJNOSCI REACT
5// ============================================
6// Ten przyklad demonstruje techniki optymalizacji
7// i profilowania komponentow React.
8
9// --- Callback do profilowania ---
10function onRenderCallback(id, phase, actualDuration, baseDuration) {
11 console.log(
12 `[Profiler] ${id} | phase: ${phase} | actual: ${actualDuration.toFixed(2)}ms | base: ${baseDuration.toFixed(2)}ms`
13 );
14}
15
16// --- Symulacja ciezkiego obliczenia ---
17function expensiveCalculation(items) {
18 let result = 0;
19 for (let i = 0; i < items.length; i++) {
20 for (let j = 0; j < 1000; j++) {
21 result += Math.sqrt(items[i].value * j + 1);
22 }
23 }
24 return result.toFixed(2);
25}
26
27// --- Komponent BEZ optymalizacji ---
28function UnoptimizedChild({ label, onClick }) {
29 const renderCount = useRef(0);
30 renderCount.current += 1;
31
32 return (
33 <div style={childStyle}>
34 <span>{label}</span>
35 <span style={renderBadge}>{renderCount.current}x renderowany</span>
36 <button onClick={onClick} style={btnStyle}>Kliknij</button>
37 </div>
38 );
39}
40
41// --- Komponent Z optymalizacja (React.memo) ---
42const OptimizedChild = React.memo(function OptimizedChild({ label, onClick }) {
43 const renderCount = useRef(0);
44 renderCount.current += 1;
45
46 return (
47 <div style={{ ...childStyle, borderColor: '#4CAF50' }}>
48 <span>{label}</span>
49 <span style={{ ...renderBadge, background: '#4CAF50' }}>{renderCount.current}x renderowany</span>
50 <button onClick={onClick} style={{ ...btnStyle, background: '#4CAF50' }}>Kliknij</button>
51 </div>
52 );
53});
54
55// --- Komponent listy z wirtualizacja (uproszczona) ---
56function VirtualizedList({ items, itemHeight = 40, containerHeight = 200 }) {
57 const [scrollTop, setScrollTop] = useState(0);
58 const containerRef = useRef(null);
59
60 const visibleStart = Math.floor(scrollTop / itemHeight);
61 const visibleCount = Math.ceil(containerHeight / itemHeight) + 1;
62 const visibleItems = items.slice(visibleStart, visibleStart + visibleCount);
63
64 return (
65 <div
66 ref={containerRef}
67 onScroll={(e) => setScrollTop(e.target.scrollTop)}
68 style={{
69 height: containerHeight, overflow: 'auto',
70 background: '#0d1b2a', borderRadius: '8px', border: '1px solid #2a3f5f',
71 }}
72 >
73 <div style={{ height: items.length * itemHeight, position: 'relative' }}>
74 {visibleItems.map((item, i) => (
75 <div
76 key={item.id}
77 style={{
78 position: 'absolute', top: (visibleStart + i) * itemHeight,
79 height: itemHeight, width: '100%', padding: '8px 12px',
80 boxSizing: 'border-box', borderBottom: '1px solid #1b2838',
81 color: '#e0e1dd', display: 'flex', alignItems: 'center',
82 }}
83 >
84 {item.name} - wartosc: {item.value}
85 </div>
86 ))}
87 </div>
88 </div>
89 );
90}
91
92// --- Glowna aplikacja ---
93function App() {
94 const [count, setCount] = useState(0);
95 const [filterText, setFilterText] = useState('');
96 const [showOptimized, setShowOptimized] = useState(false);
97
98 // Generujemy liste 500 elementow
99 const largeList = useMemo(() => {
100 return Array.from({ length: 500 }, (_, i) => ({
101 id: i,
102 name: `Misja #${i + 1}`,
103 value: Math.floor(Math.random() * 1000),
104 }));
105 }, []);
106
107 // Filtrowanie z useMemo - obliczenia wykonuja sie tylko gdy dane sie zmienia
108 const filteredItems = useMemo(() => {
109 return largeList.filter((item) =>
110 item.name.toLowerCase().includes(filterText.toLowerCase())
111 );
112 }, [largeList, filterText]);
113
114 // Ciezkie obliczenie z memoizacja
115 const expensiveResult = useMemo(() => {
116 return expensiveCalculation(filteredItems.slice(0, 50));
117 }, [filteredItems]);
118
119 // Callback BEZ memoizacji (nowa referencja przy kazdym renderowaniu)
120 const handleClickUnoptimized = () => {
121 console.log('Klik bez memoizacji');
122 };
123
124 // Callback Z memoizacja (stabilna referencja)
125 const handleClickOptimized = useCallback(() => {
126 console.log('Klik z useCallback');
127 }, []);
128
129 return (
130 <div style={appStyle}>
131 <h1 style={{ color: '#ffd700', textAlign: 'center', marginBottom: '8px' }}>
132 Profilowanie wydajnosci React
133 </h1>
134 <p style={{ textAlign: 'center', color: '#aaa', marginBottom: '20px' }}>
135 Otworz konsole przegladarki, aby zobaczyc logi z Profilera
136 </p>
137
138 {/* Sekcja 1: React.memo + useCallback */}
139 <Profiler id="MemoDemo" onRender={onRenderCallback}>
140 <div style={sectionStyle}>
141 <h2 style={headingStyle}>React.memo + useCallback</h2>
142 <p style={{ color: '#aaa', marginBottom: '12px' }}>
143 Kliknij "+1" i obserwuj, ktory komponent renderuje sie ponownie.
144 </p>
145 <div style={{ textAlign: 'center', marginBottom: '16px' }}>
146 <span style={{ fontSize: '24px', marginRight: '12px' }}>Licznik: {count}</span>
147 <button onClick={() => setCount((c) => c + 1)} style={btnAccent}>+1</button>
148 </div>
149
150 <div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
151 <div style={{ flex: 1, minWidth: '220px' }}>
152 <h3 style={{ color: '#ff6b6b' }}>Bez optymalizacji</h3>
153 <UnoptimizedChild
154 label="Komponent A"
155 onClick={handleClickUnoptimized}
156 />
157 </div>
158 <div style={{ flex: 1, minWidth: '220px' }}>
159 <h3 style={{ color: '#4CAF50' }}>Z React.memo + useCallback</h3>
160 <OptimizedChild
161 label="Komponent B"
162 onClick={handleClickOptimized}
163 />
164 </div>
165 </div>
166 </div>
167 </Profiler>
168
169 {/* Sekcja 2: useMemo na ciezkich obliczeniach */}
170 <Profiler id="UseMemoDemo" onRender={onRenderCallback}>
171 <div style={sectionStyle}>
172 <h2 style={headingStyle}>useMemo - ciezkie obliczenia</h2>
173 <p style={{ color: '#aaa', marginBottom: '12px' }}>
174 Filtrowanie i obliczenia sa memoizowane - zmiana filtra przelicza wynik.
175 </p>
176 <input
177 value={filterText}
178 onChange={(e) => setFilterText(e.target.value)}
179 placeholder="Filtruj misje..."
180 style={inputStyle}
181 />
182 <p style={{ margin: '8px 0', color: '#ffd700' }}>
183 Wynik obliczen: {expensiveResult} | Znaleziono: {filteredItems.length} misji
184 </p>
185 </div>
186 </Profiler>
187
188 {/* Sekcja 3: Wirtualizacja listy */}
189 <Profiler id="VirtualizedListDemo" onRender={onRenderCallback}>
190 <div style={sectionStyle}>
191 <h2 style={headingStyle}>Wirtualizacja listy (500 elementow)</h2>
192 <p style={{ color: '#aaa', marginBottom: '12px' }}>
193 Tylko widoczne elementy sa renderowane - przewin liste.
194 </p>
195 <label style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px', cursor: 'pointer' }}>
196 <input
197 type="checkbox"
198 checked={showOptimized}
199 onChange={() => setShowOptimized(!showOptimized)}
200 />
201 Pokaz wirtualizowana liste
202 </label>
203
204 {showOptimized ? (
205 <VirtualizedList items={filteredItems} />
206 ) : (
207 <div style={{
208 height: 200, overflow: 'auto', background: '#0d1b2a',
209 borderRadius: '8px', border: '1px solid #2a3f5f',
210 }}>
211 {filteredItems.map((item) => (
212 <div key={item.id} style={{
213 padding: '8px 12px', borderBottom: '1px solid #1b2838',
214 color: '#e0e1dd',
215 }}>
216 {item.name} - wartosc: {item.value}
217 </div>
218 ))}
219 </div>
220 )}
221 </div>
222 </Profiler>
223
224 {/* TODO: Dodaj sekcje porownujaca wydajnosc
225 dwoch podejsc do aktualizacji stanu:
226 1. Wiele osobnych useState (temperatura, cisnienie, tlen)
227 2. Jeden useState z obiektem { temperature, pressure, oxygen }
228 Uzyj komponentu Profiler do pomiaru roznic. */}
229 </div>
230 );
231}
232
233// --- Style ---
234const appStyle = {
235 background: '#0d1b2a', minHeight: '100vh', padding: '20px',
236 fontFamily: 'Arial, sans-serif', color: '#e0e1dd',
237};
238const sectionStyle = {
239 background: '#1b2838', borderRadius: '12px', padding: '20px',
240 maxWidth: '700px', margin: '16px auto', border: '1px solid #2a3f5f',
241};
242const headingStyle = { color: '#00d4ff', marginBottom: '8px' };
243const childStyle = {
244 display: 'flex', alignItems: 'center', gap: '10px',
245 padding: '10px', background: '#0d1b2a', borderRadius: '8px',
246 border: '1px solid #ff6b6b', marginBottom: '8px',
247};
248const renderBadge = {
249 background: '#ff6b6b', color: '#fff', padding: '2px 8px',
250 borderRadius: '12px', fontSize: '12px',
251};
252const btnStyle = {
253 marginLeft: 'auto', padding: '6px 14px', background: '#ff6b6b',
254 color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer',
255};
256const btnAccent = {
257 padding: '8px 20px', background: '#ffd700', color: '#000',
258 border: 'none', borderRadius: '8px', cursor: 'pointer', fontWeight: 'bold',
259};
260const inputStyle = {
261 width: '100%', padding: '8px 12px', background: '#0d1b2a',
262 border: '1px solid #2a3f5f', borderRadius: '6px', color: '#e0e1dd',
263 fontSize: '14px', boxSizing: 'border-box',
264};
265
266export 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 robi React.memo() i kiedy warto go stosować?
2. Do czego służy React Profiler w React DevTools?
Zadania praktyczne w grze
- Edytor kodu
Dynamiczne ładowanie modułów
- Układanie w poziomie
Ułóż składnię Suspense z fallback UI:
- Układanie w pionie
Uporządkuj strategie testowania według priorytetu:
- Edytor kodu
Custom hook useForm z walidacją