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:
React.memo dla komponentów, które często renderują się ponownie z tymi samymi propsamiuseMemo dla kosztownych obliczeń i tworzenia złożonych struktur danychuseCallback do stabilizowania referencji funkcji przekazywanych w dół drzewa komponentówPamię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.