Hook useMemo
Czasami komponenty muszą wykonywać kosztowne obliczenia. Na przykład w trakcie pracy z dużą listą pracowników firmy. W takim przypadku można spróbować zwiększyć wydajność komponentu przy pomocy memoizacji.
Metoda optymalizacji wykorzystywana do przyspieszenia pracy programów komputerowych. Rezultat wywołania funkcji z danymi argumentami jest zapisywany (cache). Kolejne wywołania funkcji z takimi samymi wartościami argumentów zwracają zapamiętany wynik i nie obliczają go ponownie.
Hook useMemo() wykorzystuje koncepcję memoizacji, to znaczy zwraca zapamiętany (zkeszowany) wynik obliczeń. Może to zwiększyć wydajność aplikacji, jeśli jest stosowne do zapobiegania kosztownym obliczeniom podczas renderowania.
const memoizedValue = React.useMemo(
// compute
() => computeExpensiveValue(a, b),
// deps
[a, b]
);
Hook przyjmuje dwa argumenty - anonimową funkcję, która powinna zwracać wartość (to właśnie ona będzie memoizowna) i tablicę zależności (deps). Jeżeli tablica zależności nie została zdefiniowana, wartość będzie obliczać się przy każdym renderowaniu, co w rezultacie czyni wykorzystanie useMemo() bezsensownym.
Funkcja przekazana do useMemo zostanie wywołana podczas pierwszego renderowania komponentu, a jej wynik zapamiętany i zwrócony z hooka. Jeżeli podczas następnych renderowań zależności nie zmienią się, hook nie wywoła ponownie funkcji, tylko zwróci zapisany wcześniej wynik. Jeżeli któraś z zależności się zmieniła, hook wywołuje funkcję ponownie, a następnie zapamiętuje i zwraca nową wartość.
PODSUMUJMY
- Memoizacja to zapamiętywanie wartości, aby nie trzeba było jej ciągle obliczać.
- Memoizację opłaca się stosować tylko dla kosztownych obliczeń.
- useMemo() wykonuje obliczenie wartości przynajmniej jeden raz.
- useMemo() zwraca zapamiętaną wartość.
- useMemo() uruchamia ponowne obliczenia tylko w przypadku aktualizacji którejś z zależności.
- Obowiązkowo należy przekazać zależności, w innym wypadku stosowanie useMemo() nie ma sensu.
Przeanalizuj kod w następującym przykładzie. W stanie przechowywana jest tablica łańcuchów i wartość szukanego zapytania. [Opuszczamy kod dodania elementów do tablicy i zmiany wartości zapytania].
const App = () => {
const [planets, setPlanets] = useState(["Earth", "Mars", "Jupiter", "Venus"]);
const [query, setQuery] = useState("");
const filteredPlanets = planets.filter(planet => planet.includes(query));
return (
<div>
{filteredPlanets.map(planet => (
<div key={planet}>{planet}</div>
))}
</div>
);
};
Za każdym razem, gdy zmieni się wartość planets lub query, komponent będzie renderowany ponownie. W rezultacie wartość filteredPlanets zostanie obliczona ponownie. To zupełnie normalne! W takim przypadku niepotrzebna jest żadna memoizacja.
Teraz wyobraź sobie, że komponent <App> zawiera dodatkowy stan lub otrzymuje jakiś props, nie wpływający na planety.
const App = ({ someProp }) => {
const [planets, setPlanets] = useState(["Earth", "Mars", "Jupiter", "Venus"]);
const [query, setQuery] = useState("");
const [clicks, setClicks] = useState(0);
const filteredPlanets = planets.filter(planet => planet.includes(query));
return (
<div>
<div>Some prop: {someProp}</div>
<button onClick={() => setClicks(clicks + 1)}>
Number of clicks: {clicks}
</button>
<div>
{filteredPlanets.map(planet => (
<div key={planet}>{planet}</div>
))}
</div>
</div>
);
};
Za każdym razem, gdy zmienia się stan clicks lub props someProp, komponent będzie renderowany ponownie. Doprowadzi to do ponownego obliczenia filteredPlanets i przerenderowania drzewa komponentów, mimo iż wartości planets i query nie zmieniły się! W takim wypadku może być warto memoizować obliczanie filteredPlanets.
import { useMemo } from "react";
const App = ({ someProp }) => {
const [planets, setPlanets] = useState(["Earth", "Mars", "Jupiter", "Venus"]);
const [query, setQuery] = useState("");
const [clicks, setClicks] = useState(0);
const filteredPlanets = useMemo(
() => planets.filter(planet => planet.includes(query)),
[planets, query]
);
return (
<div>
<div>Some prop: {someProp}</div>
<button onClick={() => setClicks(clicks + 1)}>
Number of clicks: {clicks}
</button>
<div>
{filteredPlanets.map(planet => (
<div key={planet}>{planet}</div>
))}
</div>
</div>
);
};
To samo dotyczy kosztownych operacji, na przykład wykorzystanie długiego cyklu for. Kosztowne obliczenia mogą być stratą czasu, co z pewnością doprowadzi do pogorszenia responsywności interfejsu.
WIĘCEJ NIE ZNACZY LEPIEJ: Nie trzeba memoizować wszystkiego, gdyż - paradoksalnie - może to doprowadzić do utraty wydajności. Memoizacja również zajmuje pamięć obliczeniową. Ciągłe wykonywanie prostych obliczeń jest wciąż "tańsze" niż ich memoizacja. Używaj więc useMemo() z rozwagą, a doświadczenie przyjdzie z czasem.
Dokumentacja useMemo z dwoma przykładami