W przestrzeni kosmicznej, zasoby są ograniczone i trzeba je mądrze wykorzystywać. Podobnie w świecie React, wydajność aplikacji ma kluczowe znaczenie. Memoizacja to technika optymalizacyjna, która pozwala na "zapamiętywanie" wyników kosztownych operacji i ponowne ich wykorzystanie, gdy wejściowe dane nie uległy zmianie - podobnie jak zapisywanie i wykorzystywanie obliczeń trajektorii kosmicznej zamiast wykonywania ich od nowa.
Memoizacja w React to mechanizm optymalizacji wydajności, który pozwala:
`React.memo` to komponent wyższego rzędu (HOC), który opakowuje komponent i zapobiega jego ponownemu renderowaniu, jeśli jego propsy nie uległy zmianie.
1import React from 'react';
2
3// Komponent bez memoizacji
4function PlanetInfo({ name, diameter, distance }) {
5 console.log(\`Renderowanie informacji o planecie: \${name}\`);
6 return (
7 <div className="planet-info">
8 <h2>{name}</h2>
9 <p>Średnica: {diameter} km</p>
10 <p>Odległość od Słońca: {distance} mln km</p>
11 </div>
12 );
13}
14
15// Ten sam komponent z memoizacją
16const MemoizedPlanetInfo = React.memo(PlanetInfo);
17
18// Komponent rodzic
19function SolarSystem() {
20 const [count, setCount] = useState(0);
21
22 return (
23 <div>
24 <button onClick={() => setCount(count + 1)}>
25 Licznik: {count}
26 </button>
27
28 {/* Ten komponent będzie renderowany ponownie tylko gdy jego propsy się zmienią */}
29 <MemoizedPlanetInfo
30 name="Mars"
31 diameter={6779}
32 distance={228}
33 />
34 </div>
35 );
36}Zauważ, że w powyższym przykładzie, mimo że komponent nadrzędny `SolarSystem` renderuje się ponownie przy każdym kliknięciu przycisku, komponent `MemoizedPlanetInfo` nie będzie ponownie renderowany, ponieważ jego propsy pozostają niezmienione.
Domyślnie `React.memo` porównuje propsy za pomocą płytkiego porównania (shallow comparison). Możesz jednak dostarczyć własną funkcję porównującą:
1const MemoizedPlanetInfo = React.memo(
2 PlanetInfo,
3 (prevProps, nextProps) => {
4 // Zwróć true, jeśli chcesz zapobiec ponownemu renderowaniu
5 // Zwróć false, jeśli chcesz ponownie wyrenderować komponent
6 return prevProps.name === nextProps.name &&
7 prevProps.diameter === nextProps.diameter;
8 // Zauważ, że ignorujemy zmianę distance - komponent nie wyrenderuje się ponownie
9 // kiedy zmieni się tylko ta właściwość
10 }
11);Hook `useMemo` pozwala na zapamiętanie wyniku kosztownej operacji i ponowne użycie go, o ile zależności nie uległy zmianie.
1import React, { useState, useMemo } from 'react';
2
3function AsteroidCalculator({ asteroids }) {
4 const [filter, setFilter] = useState('all');
5
6 // Kosztowne obliczenie - memoizowane dla lepszej wydajności
7 const dangerousAsteroids = useMemo(() => {
8 console.log('Obliczanie niebezpiecznych asteroid...');
9 return asteroids.filter(asteroid => {
10 // Symulacja kosztownych obliczeń
11 let isDangerous = false;
12 for (let i = 0; i < 10000; i++) {
13 if (asteroid.velocity > 20 && asteroid.size > 1000 && asteroid.distance < 50000000) {
14 isDangerous = true;
15 }
16 }
17 return isDangerous;
18 });
19 }, [asteroids]); // Obliczenie zostanie wykonane ponownie tylko gdy zmieni się tablica asteroids
20
21 // To obliczenie również jest memoizowane, ale zależy od filtru
22 const displayedAsteroids = useMemo(() => {
23 console.log('Filtrowanie asteroid...');
24 if (filter === 'all') return asteroids;
25 if (filter === 'dangerous') return dangerousAsteroids;
26 return [];
27 }, [filter, asteroids, dangerousAsteroids]);
28
29 return (
30 <div>
31 <div>
32 <button onClick={() => setFilter('all')}>Wszystkie</button>
33 <button onClick={() => setFilter('dangerous')}>Niebezpieczne</button>
34 </div>
35 <h2>Asteroidy ({displayedAsteroids.length}):</h2>
36 <ul>
37 {displayedAsteroids.map(asteroid => (
38 <li key={asteroid.id}>{asteroid.name}</li>
39 ))}
40 </ul>
41 </div>
42 );
43}Hook `useCallback` jest podobny do `useMemo`, ale zamiast zapamiętywać wynik funkcji, zapamiętuje samą funkcję. Jest szczególnie przydatny, gdy przekazujesz funkcje jako propsy do memoizowanych komponentów potomnych.
1import React, { useState, useCallback } from 'react';
2
3function MissionControl() {
4 const [missionStatus, setMissionStatus] = useState('preparing');
5 const [fuelLevel, setFuelLevel] = useState(100);
6
7 // Z useCallback, funkcja jest tworzona ponownie tylko wtedy, gdy zmienia się fuelLevel
8 const startMission = useCallback(() => {
9 if (fuelLevel > 20) {
10 setMissionStatus('in-progress');
11 console.log(\`Misja rozpoczęta z poziomem paliwa: \${fuelLevel}%\`);
12 } else {
13 console.log('Za mało paliwa do rozpoczęcia misji');
14 }
15 }, [fuelLevel]); // Zależność: funkcja utworzona ponownie, gdy zmieni się fuelLevel
16
17 const abortMission = useCallback(() => {
18 setMissionStatus('aborted');
19 console.log('Misja przerwana!');
20 }, []); // Pusta tablica zależności - funkcja utworzona tylko raz
21
22 return (
23 <div>
24 <h2>Status misji: {missionStatus}</h2>
25 <p>Poziom paliwa: {fuelLevel}%</p>
26
27 <button onClick={() => setFuelLevel(prev => Math.max(0, prev - 10))}>
28 Zużyj paliwo
29 </button>
30
31 {/* Przekazanie memoizowanych funkcji jako propsów */}
32 <MemoizedControlPanel
33 onStart={startMission}
34 onAbort={abortMission}
35 fuelLevel={fuelLevel}
36 />
37 </div>
38 );
39}
40
41// Memoizowany komponent potomny, który otrzymuje funkcje jako propsy
42const ControlPanel = React.memo(({ onStart, onAbort, fuelLevel }) => {
43 console.log('Renderowanie panelu kontrolnego');
44
45 return (
46 <div className="control-panel">
47 <h3>Panel sterowania</h3>
48 <button onClick={onStart} disabled={fuelLevel <= 20}>
49 Start
50 </button>
51 <button onClick={onAbort}>
52 Przerwij misję
53 </button>
54 </div>
55 );
56});
57
58const MemoizedControlPanel = React.memo(ControlPanel);| Technika | Zastosowanie | Kiedy używać | |----------|--------------|--------------| | React.memo | Komponent | Gdy komponent często otrzymuje te same propsy | | useMemo | Wartość | Przy kosztownych obliczeniach i złożonych strukturach danych | | useCallback | Funkcja | Gdy przekazujesz funkcje jako propsy do memoizowanych komponentów |
1// Nieoptymalne - zbyt prosta wartość do memoizacji
2const count = useMemo(() => 1 + 2, []);
3
4// Nieoptymalne - prosta funkcja, która nie potrzebuje memoizacji
5const handleClick = useCallback(() => {
6 console.log('Kliknięto!');
7}, []);1// Niebezpieczne - brakuje zależności 'value'
2const processedData = useMemo(() => {
3 return heavyProcessing(value);
4}, []); // Powinno być [value]1// Może zmniejszyć skuteczność memoizacji
2const result = useMemo(() => {
3 return computeResult(a, b);
4}, [a, b, c, d, e]); // Zbyt dużo zależności - memoizacja może być nieskutecznaMemoizacja w React to potężna technika optymalizacyjna, która - podobnie jak oszczędzanie zasobów na statku kosmicznym - pomaga efektywnie zarządzać wydajnością aplikacji. Korzystaj z niej mądrze:
Pamiętaj jednak, że jak każda technika optymalizacyjna, memoizacja ma swój koszt i powinna być stosowana rozważnie, tam gdzie faktycznie przynosi korzyści wydajnościowe.