Usamos cookies para mejorar tu experiencia en el sitio
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.

Ir a CodeWorlds