React: Do czego służy memo?
Opublikowano: 11.07.2023 - tagi: React JavaScript Komponent Wydajność
Czym jest memo?
Kiedy stan komponentu się zmienia, React przerysowuje go na nowo. Dzieje się to także z innymi komponentami, które komponent-rodzic zawiera.
Może się zdarzyć, że takie przerysowywanie komponentu za każdym razem nie jest dobrym pomysłem. Wydajność aplikacji może spaść. I w takich przypadkach można skorzystać z memo. Po jego użyciu React zapamiętuje komponent i pomija (jeśli nie jest to potrzebne) ponowne przerysowanie komponentu.
Z memo należy korzystać rozważnie — tylko wtedy, gdy ma to sens.
Jak działa memo?
Przykład użycia:
import React, { memo } from 'react';
function User({ name, age }) {
return (
<>
<div>Name: <strong>{name}</strong></div>
<div>Age: <strong>{age}</strong></div>
</>
);
}
const MemoizedUser = memo(User);
<MemoizedUser name="Janusz" age="18"></MemoizedUser>
Żeby komponent został "zapamiętany" przekazujemy go do funkcji memo. Od tego momentu, gdy użyjemy tak przygotowany komponent (MemoizedUser), React będzie używać jego zapamiętaną wersję, dopóty dopóki jego właściwości: name lub age będą mieć te same wartości. Jeśli wartość dla przynajmniej jednego parametru komponentu zostanie zmieniona, to zostanie on przerysowany i zapamiętany dla nowych wartości.
Składnia memo
Funkcja memo zawiera więcej niż jeden parametr:
memo(Component, arePropsEqual?)
Component — Tutaj przekazujemy komponent, który chcemy, żeby został zapamiętany.
arePropsEqual — Jest to parametr opcjonalny. To funkcja, która informuje React, czy komponent powinien zostać przerysowany. Przyjmuje dwa parametry: pierwszy to wartości z poprzedniego stanu komponentu. Drugi aktualne wartości stanu. Ta funkcja powinna zwrócić true jeśli oba stany mają te samo wartości — nic się nie zmieniło, więc komponent nie musi być przerysowany.
Przykład takiej funkcji:
function areUserPropsEqual(prev, next) {
return prev.name === next.name &&
prev.age === next.age;
}
const MemoizedUser = memo(User, areUserPropsEqual);
Kiedy używać memo?
- Komponent jest "czysty" - Dla tych samych wartości zawsze wyświetla ten sam wynik.
- Komponent jest często przerysowywany.
- Komponent staje się coraz większy. Przez co jego częste przerysowywanie staje się kosztowne.
- Komponent jest często przerysowywany z tymi samymi wartościami.
Kiedy nie używać memo?
- Komponent zwykle jest wyświetlany z różnymi wartościami.
- Komponent nie jest duży i nie robi czasochłonnych obliczeń.
Uwaga na funkcje zwrotne!
Kiedy przekazujesz do komponentu funkcję zwrotną (ang. callback) i zamierzasz skorzystać z memo, uważaj, jak to robisz. Możesz popsuć zapamiętywanie komponentu!
Przykład:
import React, { useState, memo } from 'react';
function User({ name, age, onSendMessage }) {
return (
<>
<div>Name: <strong>{name}</strong></div>
<div>Age: <strong>{age}</strong></div>
<button onClick={onSendMessage}>Send message</button>
</>
);
}
const MemoizedUser = memo(User);
const App = () => {
const [user, setUser] = useState({
name: 'Janusz',
age: 18
});
return (
<>
<div>
<button onClick={() => setUser({
name: 'Janusz',
age: 15.99
})}>Janusz</button>
<button onClick={() => setUser({
name: 'Grażyna',
age: 55
})}>Grażyna</button>
</div>
<MemoizedUser
name={user.name}
age={user.age}
onSendMessage={
() => console.log(`Send message to: ${user.name}`)
}></MemoizedUser>
</>
);
}
W powyższym przykładzie zapamiętywanie komponentu przez memo jest popsute. Dodaj console.log w komponencie User i klikaj w przycisk "Janusz". Komponent MemoizedUser cały czas jest przerysowywany na nowo? Co się dzieje?!
Problem jest w tej linijce:
onSendMessage={() => console.log(`Send message to: ${user.name}`)
Za każdym razem, gdy stan komponentu App się zmienia do onSendMessage przekazywana jest nowa instancja funkcji zwrotnej. A skoro nowa wartość to komponent MemoizedUser zostanie przerysowany na nowo! I tak za każdym razem...
Można to rozwiązać za pomocą useCallback:
import React, { useState, useCallback, memo } from 'react';
const App = () => {
const [user, setUser] = useState({
name: 'Janusz',
age: 18
});
const sendMessage = useCallback(() => {
console.log(`Send message to: ${user.name}`)
}, [user.name]);
return (
<>
<div>
<button onClick={() => setUser({
name: 'Janusz',
age: 15.99
})}>Janusz</button>
<button onClick={() => setUser({
name: 'Grażyna',
age: 55
})}>Grażyna</button>
</div>
<MemoizedUser
name={user.name}
age={user.age}
onSendMessage={sendMessage}></MemoizedUser>
</>
);
}