Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Memoizacja - React.memo, useMemo i useCallback

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.

Czym jest memoizacja w React?

Memoizacja w React to mechanizm optymalizacji wydajności, który pozwala:

  • Unikać niepotrzebnych ponownych renderowań komponentów
  • Zapobiegać ponownemu wykonywaniu kosztownych obliczeń
  • Stabilizować referencje do funkcji i obiektów

React.memo - memoizacja komponentów

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.

Podstawowe użycie React.memo

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.

Niestandardowe porównanie propsów

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);

useMemo - memoizacja wartości

Hook

useMemo
pozwala na zapamiętanie wyniku kosztownej operacji i ponowne użycie go, o ile zależności nie uległy zmianie.

Kiedy używać useMemo?

  1. Przy kosztownych obliczeniach
  2. Przy tworzeniu obiektów referencyjnych jako propsy dla komponentów potomnych
  3. Przy tworzeniu obszernych struktur danych
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}

useCallback - memoizacja funkcji

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);

Porównanie technik memoizacji i kiedy ich używać

| 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 |

Typowe błędy i pułapki

1. Nadmierna memoizacja

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}, []);

2. Zapominanie o zależnościach

1// Niebezpieczne - brakuje zależności 'value'
2const processedData = useMemo(() => {
3  return heavyProcessing(value);
4}, []); // Powinno być [value]

3. Zbyt wiele zależności

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ć nieskuteczna

Mądrze korzystaj z memoizacji

  1. Nie optymalizuj przedwcześnie - używaj najpierw profilerów, aby zidentyfikować rzeczywiste problemy wydajnościowe
  2. Stosuj memoizację selektywnie - tylko tam, gdzie jest faktycznie potrzebna
  3. Pamiętaj o koszcie - memoizacja również ma swój koszt, który może przewyższać zyski dla prostszych operacji

Podsumowanie

Memoizacja 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:

  • Używaj
    React.memo
    dla komponentów, które często renderują się ponownie z tymi samymi propsami
  • Stosuj
    useMemo
    dla kosztownych obliczeń i tworzenia złożonych struktur danych
  • Wykorzystuj
    useCallback
    do stabilizowania referencji funkcji przekazywanych w dół drzewa komponentów

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.

Przejdź do CodeWorlds