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

Profilowanie wydajności

11 min czytania
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:

  1. Instalacja React DevTools

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

  1. Uruchomienie profilowania:

    • Otwórz DevTools w Chrome (F12)
    • Przejdź do zakładki "Performance"
    • Kliknij przycisk nagrywania
    • Wykonaj akcje w aplikacji
    • Zatrzymaj nagrywanie
  2. 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:

  1. Uruchom profilowanie
  2. Przewiń listę, aby zobaczyć, jak reaguje aplikacja
  3. Zatrzymaj profilowanie
  4. 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:

  1. Nagrywamy sesję podczas normalnego użytkowania panelu
  2. Analizujemy wyniki w Profilerze
  3. Odkrywamy, że:
    • Komponent TelemetryChart renderuje się bardzo długo (>100ms)
    • Komponent EventsList renderuje się za każdym razem, gdy przychodzą nowe dane telemetryczne
    • Funkcja calculateTrends zajmuje dużo czasu procesora

Krok 3: Optymalizacja

Na podstawie wyników profilowania wprowadzamy następujące optymalizacje:

  1. 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});
  1. 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);
  1. 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ę:

  1. Czas renderowania TelemetryChart spadł z >100ms do ~15ms
  2. Komponent EventsList nie renderuje się już przy aktualizacjach danych telemetrycznych
  3. 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

  1. Profiluj w środowisku podobnym do produkcyjnego

    • Uruchamiaj aplikację w trybie produkcyjnym (npm run build i serwuj zbudowane pliki)
    • Testuj na urządzeniach podobnych do tych używanych przez użytkowników
  2. Ustal punkty odniesienia

    • Zmierz wydajność przed i po optymalizacjach
    • Określ konkretne cele wydajnościowe (np. czas pierwszego renderowania < 2s)
  3. Profiluj regularnie

    • Włącz profilowanie do cyklu rozwoju aplikacji
    • Monitoruj wydajność po wprowadzeniu większych zmian
  4. 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)
  5. 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:

  1. Używaj odpowiednich narzędzi - React DevTools Profiler, Chrome Performance Panel
  2. Identyfikuj wąskie gardła - znajdź komponenty, które renderują się zbyt często lub zbyt długo
  3. Stosuj odpowiednie techniki optymalizacji - memoizacja, unikanie zbędnych renderowań, wirtualizacja list
  4. Mierz skuteczność optymalizacji - porównuj wydajność przed i po zmianach
  5. 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. 1. Co robi React.memo() i kiedy warto go stosować?

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

Przydatne artykuły