Dowiedz się, jak okiełznać użycie ReactaCallback Hook
Opublikowany: 2022-04-27Nie jest tajemnicą, że React.js stał się w ostatnich latach bardzo popularny. Jest to obecnie biblioteka JavaScript wybierana przez wielu najwybitniejszych graczy w Internecie, w tym Facebooka i WhatsAppa.
Jednym z głównych powodów jego powstania było wprowadzenie hooków w wersji 16.8. Hooki Reacta pozwalają na korzystanie z funkcjonalności Reacta bez pisania komponentów klas. Teraz funkcjonalne komponenty z haczykami stały się strukturą docelową dla programistów do pracy z Reactem.
W tym poście na blogu zagłębimy się w jeden konkretny haczyk — useCallback
— ponieważ dotyka on podstawowej części programowania funkcjonalnego znanej jako zapamiętywanie. Dowiesz się dokładnie, jak i kiedy wykorzystać hak useCallback
i jak najlepiej wykorzystać jego możliwości zwiększające wydajność.
Gotowy? Zanurzmy się!
Co to jest zapamiętywanie?
Zapamiętywanie ma miejsce, gdy złożona funkcja przechowuje swoje dane wyjściowe, więc następnym razem, gdy zostanie wywołana z tym samym wejściem. Jest to podobne do buforowania, ale na poziomie bardziej lokalnym. Może pominąć wszelkie złożone obliczenia i szybciej zwrócić dane wyjściowe, ponieważ zostały już obliczone.
Może to mieć znaczący wpływ na alokację pamięci i wydajność, a to obciążenie ma złagodzić hak useCallback
.
UseCallback vs useMemo w React
W tym miejscu warto wspomnieć, że useCallback
ładnie łączy się z innym hakiem o nazwie useMemo
. Omówimy je obie, ale w tym artykule skupimy się na useCallback
jako głównym temacie.
Kluczową różnicą jest to, że useMemo
zwraca zapamiętaną wartość, podczas gdy useCallback
zwraca zapamiętaną funkcję. Oznacza to, że useMemo
służy do przechowywania obliczonej wartości, podczas gdy useCallback
zwraca funkcję, którą można później wywołać.
Te haki zwrócą wersję z pamięci podręcznej, chyba że zmieni się jedna z ich zależności (np. stan lub właściwości).
Przyjrzyjmy się dwóm funkcjom w akcji:
import { useMemo, useCallback } from 'react' const values = [3, 9, 6, 4, 2, 1] // This will always return the same value, a sorted array. Once the values array changes then this will recompute. const memoizedValue = useMemo(() => values.sort(), [values]) // This will give me back a function that can be called later on. It will always return the same result unless the values array is modified. const memoizedFunction = useCallback(() => values.sort(), [values])
Powyższy fragment kodu jest wymyślonym przykładem, ale pokazuje różnicę między dwoma wywołaniami zwrotnymi:
-
memoizedValue
stanie się tablicą[1, 2, 3, 4, 6, 9]
. Dopóki zmienna wartości pozostaje,memoizedValue
i nigdy nie zostanie przeliczone. -
memoizedFunction
będzie funkcją, która zwróci tablicę[1, 2, 3, 4, 6, 9]
.
Wspaniałą rzeczą w tych dwóch wywołaniach zwrotnych jest to, że są one buforowane i zawieszają się, dopóki nie zmieni się tablica zależności. Oznacza to, że podczas renderowania nie będą zbierane śmieci.
tweetowaćRenderowanie i reagowanie
Dlaczego zapamiętywanie jest ważne w przypadku Reacta?
Ma to związek z tym, jak React renderuje twoje komponenty. React używa wirtualnego DOM przechowywanego w pamięci do porównywania danych i decydowania, co zaktualizować.
Wirtualny DOM pomaga React zwiększyć wydajność i zapewnia szybkość działania aplikacji. Domyślnie, jeśli jakakolwiek wartość w komponencie ulegnie zmianie, cały komponent zostanie ponownie wyrenderowany. To sprawia, że React „reaguje” na dane wprowadzane przez użytkownika i umożliwia aktualizację ekranu bez ponownego ładowania strony.
Nie chcesz renderować swojego komponentu, ponieważ zmiany nie wpłyną na ten komponent. Tutaj przydaje się zapamiętywanie poprzez useCallback
i useMemo
.
Kiedy React ponownie renderuje twój komponent, odtwarza również funkcje, które zadeklarowałeś w swoim komponencie.
Zauważ, że porównując równość funkcji z inną funkcją, zawsze będą one fałszywe. Ponieważ funkcja jest również obiektem, będzie się równać tylko sobie:
// these variables contain the exact same function but they are not equal const hello = () => console.log('Hello Matt') const hello2 = () => console.log('Hello Matt') hello === hello2 // false hello === hello // true
Innymi słowy, kiedy React ponownie renderuje twój komponent, zobaczy wszystkie funkcje, które są zadeklarowane w twoim komponencie jako nowe funkcje.
W większości przypadków jest to w porządku, a proste funkcje są łatwe do obliczenia i nie wpływają na wydajność. Ale w innych przypadkach, gdy nie chcesz, aby funkcja była postrzegana jako nowa funkcja, możesz liczyć na pomoc useCallback
.
Możesz pomyśleć: „Kiedy nie chciałbym, aby funkcja była postrzegana jako nowa funkcja?” Cóż, są pewne przypadki, w których useCallback
ma więcej sensu:

- Przekazujesz funkcję do innego komponentu, który jest również zapamiętywany (
useMemo
) - Twoja funkcja ma stan wewnętrzny, który musi zapamiętać
- Twoja funkcja jest zależnością innego hooka, na przykład
useEffect
Wydajność Korzyści płynące z użycia ReactOddzwoń
Gdy useCallback
jest odpowiednio używany, może przyspieszyć działanie aplikacji i zapobiec ponownemu renderowaniu komponentów, jeśli nie są potrzebne.
Załóżmy na przykład, że masz komponent, który pobiera dużą ilość danych i jest odpowiedzialny za wyświetlanie tych danych w formie wykresu lub wykresu, w następujący sposób:
Załóżmy, że komponent nadrzędny komponentu wizualizacji danych jest ponownie renderowany, ale zmienione właściwości lub stan nie mają wpływu na ten komponent. W takim przypadku prawdopodobnie nie chcesz ani nie musisz ponownie renderować i pobierać wszystkich danych. Unikanie tego ponownego renderowania i ponownego pobierania może zaoszczędzić przepustowość użytkownika i zapewnić płynniejszą obsługę.
Wady React useCallback
Chociaż ten hak może pomóc Ci poprawić wydajność, ma również swoje pułapki. Kilka rzeczy do rozważenia przed użyciem useCallback
(i useMemo
) to:
- Zbieranie śmieci: inne funkcje, które nie zostały jeszcze zapamiętane, zostaną odrzucone przez React, aby zwolnić pamięć.
- Alokacja pamięci: Podobnie jak w przypadku wyrzucania śmieci, im więcej posiadasz zapamiętanych funkcji, tym więcej pamięci będzie wymagane. Dodatkowo, za każdym razem, gdy używasz tych wywołań zwrotnych, w React znajduje się garść kodu, który potrzebuje jeszcze więcej pamięci, aby zapewnić buforowane dane wyjściowe.
- Złożoność kodu: Kiedy zaczynasz zawijać funkcje w te zaczepy, natychmiast zwiększasz złożoność kodu. Wymaga to teraz lepszego zrozumienia, dlaczego te haki są używane, i potwierdzenia, że są używane poprawnie.
Świadomość powyższych pułapek może oszczędzić ci bólu głowy związanego z samotnym natknięciem się na nie. Rozważając użycie useCallback
, upewnij się, że korzyści związane z wydajnością przeważą nad wadami.
React useCallback Przykład
Poniżej znajduje się prosta konfiguracja z komponentem Button i komponentem Counter. Licznik ma dwie części stanu i renderuje dwa składniki Button, z których każdy zaktualizuje oddzielną część stanu składników licznika.
Komponent Button ma dwie właściwości: handleClick
i name. Za każdym razem, gdy Button jest renderowany, loguje się do konsoli.
import { useCallback, useState } from 'react' const Button = ({handleClick, name}) => { console.log(`${name} rendered`) return <button onClick={handleClick}>{name}</button> } const Counter = () => { console.log('counter rendered') const [countOne, setCountOne] = useState(0) const [countTwo, setCountTwo] = useState(0) return ( <> {countOne} {countTwo} <Button handleClick={() => setCountOne(countOne + 1)} name="button1" /> <Button handleClick={() => setCountTwo(countTwo + 1)} name="button1" /> </> ) }
W tym przykładzie za każdym razem, gdy klikniesz dowolny przycisk, zobaczysz to w konsoli:
// counter rendered // button1 rendered // button2 rendered
Teraz, jeśli zastosujemy useCallback
do naszych funkcji handleClick
i zawiniemy nasz Button w React.memo
, możemy zobaczyć, co zapewnia nam useCallback
. React.memo
jest podobny do useMemo
i pozwala nam zapamiętywać komponent.
import { useCallback, useState } from 'react' const Button = React.memo(({handleClick, name}) => { console.log(`${name} rendered`) return <button onClick={handleClick}>{name}</button> }) const Counter = () => { console.log('counter rendered') const [countOne, setCountOne] = useState(0) const [countTwo, setCountTwo] = useState(0) const memoizedSetCountOne = useCallback(() => setCountOne(countOne + 1), [countOne) const memoizedSetCountTwo = useCallback(() => setCountTwo(countTwo + 1), [countTwo]) return ( <> {countOne} {countTwo} <Button handleClick={memoizedSetCountOne} name="button1" /> <Button handleClick={memoizedSetCountTwo} name="button1" /> </> ) }
Teraz, gdy klikniemy jeden z przycisków, zobaczymy tylko przycisk, który kliknęliśmy, aby zalogować się do konsoli:
// counter rendered // button1 rendered // counter rendered // button2 rendered
Zastosowaliśmy memoization do naszego komponentu przycisku, a wartości właściwości, które są do niego przekazywane, są postrzegane jako równe. Dwie funkcje handleClick
są buforowane i będą postrzegane jako ta sama funkcja przez React do momentu zmiany wartości elementu w tablicy zależności (np. countOne
, countTwo
).
Streszczenie
useCallback
i useMemo
są fajne, pamiętaj, że mają one specyficzne przypadki użycia — nie powinieneś zawijać każdej funkcji w te hooki. Jeśli funkcja jest złożona obliczeniowo, zależność innego hooka lub właściwości przekazanej do zapamiętanego komponentu jest dobrym wskaźnikiem, do którego warto sięgnąć po useCallback
.
Mamy nadzieję, że ten artykuł pomógł ci zrozumieć tę zaawansowaną funkcjonalność Reacta i pomógł ci zyskać więcej pewności w programowaniu funkcjonalnym!