useCallback — to hook React, który zwraca zmemorizowaną wersję funkcji, która nie zmienia się między renderami, dopóki nie zmienią się jej zależności. W przeciwieństwie do zwykłego deklarowania funkcji wewnątrz komponentu (które tworzy nową funkcję przy każdym renderze), useCallback stabilizuje referencję do funkcji, co zapobiega niepotrzebnym rerenderom komponentów potomnych zoptymalizowanych przez React.memo. Według React Documentation (2025), useCallback jest przydatny tylko w połączeniu z React.memo lub hookami zależnymi od stabilnej referencji.
Najważniejsze
useCallback — to hook dodany w React 16.8, który memoizuje funkcję: zwraca tę samą referencję, dopóki zależności się nie zmienią. Bez useCallback każde deklarowanie funkcji wewnątrz komponentu tworzy nowy obiekt funkcji przy każdym renderze. Dla prymitywów jest to niezauważalne, ale przy przekazywaniu takich callbacków do komponentów potomnych zoptymalizowanych przez React.memo każda nowa referencja powoduje rerender komponentu potomnego.
Składniowo useCallback jest równoważny useMemo dla funkcji: useCallback(fn, deps) — to skrót dla useMemo(() => fn, deps). React przechowuje zmemorizowaną funkcję w wewnętrznym magazynie węzła fiber i porównuje zależności przy każdym renderze. Jeśli zależności się nie zmieniły (Object.is dla każdego elementu), zwracana jest poprzednia funkcja.
Według React Documentation — useCallback (2025), nie warto owijać każdej funkcji w useCallback. Hook ma swoją cenę: wywołanie hooka, porównanie zależności i alokacja pamięci pod tablicę zależności. Jeśli komponent jest prosty i nie ma głębokich drzew z React.memo — useCallback będzie tylko spowalniać aplikację. Optymalizacja powinna być mierzalna, a nie intuicyjna.
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Stabilna referencja — ta sama funkcja, dopóki deps się nie zmienią
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <Child onClick={handleClick} />;
}
Memoizacja w useCallback opiera się na cache'owaniu wyniku wywołania funkcji. React zachowuje domknięcie utworzone w momencie pierwszego rendera i zwraca je przy kolejnych renderach, dopóki zależności pozostają niezmienne. Wewnątrz węzła fiber każde wywołanie useCallback tworzy węzeł na liście powiązanej hooków, gdzie przechowywane są poprzednie zależności i zmemorizowana wartość.
Proces porównywania zależności odbywa się ściśle przez Object.is — to porównanie powierzchowne, bez głębokiego sprawdzania obiektów i tablic. Jeśli w zależnościach jest obiekt lub tablica, nowa referencja przy każdym renderze będzie uznawana za zmianę. Dlatego w tablicy zależności trzeba przekazywać wartości prymitywne lub stabilne referencje (na przykład z useRef lub useMemo).
Według React Core Team — Optimization Guide (2024), koszt memoizacji obejmuje trzy elementy: alokację tablicy zależności przy każdym renderze, przejście i porównanie elementów przez Object.is oraz potencjalny narzut ze zbierania śmieci przy odtwarzaniu. Dla komponentu z setkami opakowań useCallback może to być zauważalne — dlatego selektywność w użyciu hooka jest krytyczna.
| Scenariusz | Bez useCallback | Z useCallback |
|---|---|---|
| Tworzenie funkcji | Nowa przy każdym renderze | Ta sama przy stabilnych deps |
| Przekazanie do React.memo | Dziecko jest rerenderowane | Dziecko nie jest rerenderowane |
| W tablicy useEffect | Efekt jest restartowany | Efekt jest stabilny |
| Narzut | Minimalny | Porównanie zależności + pamięć |
Powszechny jest mit, że useCallback automatycznie poprawia wydajność. Tak naprawdę w izolacji (bez React.memo) useCallback nawet nieco spowalnia aplikację z powodu kosztu porównywania zależności. Realną korzyść hook przynosi tylko w trzech scenariuszach: zapobieganie rerenderom komponentów React.memo, stabilność callbacków w useEffect oraz przekazywanie do custom hooków, które zależą od reference equality.
Zasada jest prosta: dopóki nie wykryjesz problemu z wydajnością przez React DevTools Profiler — nie używaj useCallback. Zespół React wielokrotnie podkreślał, że przedwczesna optymalizacja jest źródłem zła. Najpierw napisz czysty kod bez memoizacji, zmierz, znajdź wąskie gardło w profilerze i dopiero potem dodawaj useCallback tam, gdzie naprawdę jest potrzebny.
// Mierzalna optymalizacja: Child jest owinięty w React.memo
const Child = React.memo(({ onClick }) => {
console.log('Child został rerenderowany');
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const handleClick = useCallback(() => {
console.log('kliknięto');
}, []);
return <Child onClick={handleClick} />;
}
Według Dan Abramov — Before You memo() (2024), ponad 90% przypadków użycia useCallback w projektach open-source jest nadmiarowych. Deweloperzy owijają każdą funkcję „na wszelki wypadek", nie mierząc efektu. Alternatywa: jeśli komponent potomny jest ciężki i jego rerender jest kosztowny — React.memo + useCallback są uzasadnione. Jeśli komponent potomny jest lekki — rerender jest tańszy niż porównanie zależności.
Pierwszy scenariusz — React.memo. Jeśli komponent potomny jest owinięty w React.memo i przyjmuje funkcję callback jako prop, bez useCallback komponent potomny będzie przerysowywany przy każdym renderze rodzica, nawet jeśli jego własne dane się nie zmieniły. useCallback stabilizuje referencję, dzięki czemu React.memo może poprawnie pominąć rerender.
Drugi scenariusz — useEffect z callbackiem w zależnościach. Jeśli funkcja jest przekazywana do tablicy zależności useEffect, każda nowa referencja będzie restartować efekt. useCallback gwarantuje, że referencja jest stabilna, a efekt wykonuje się tylko przy zmianie rzeczywistych danych, a nie przy każdym renderze. Jest to szczególnie ważne dla subskrypcji i zapytań.
// useCallback dla stabilnej zależności useEffect
const fetchData = useCallback(async (id) => {
const res = await fetch(`/api/${id}`);
setData(res.data);
}, []); // stabilna referencja, nigdy nie jest odtwarzana
useEffect(() => {
fetchData(props.id);
}, [props.id, fetchData]); // efekt uruchamia się tylko przy zmianie props.id
Główna różnica między useCallback a useMemo polega na tym, co każdy z nich memoizuje. useCallback memoizuje funkcję: useCallback(fn, deps) zwraca fn (tę samą lub poprzednią wersję). useMemo memoizuje wynik wywołania funkcji: useMemo(() => computeExpensive(a, b), [a, b]) zwraca obliczoną wartość, a nie funkcję.
Technicznie useCallback — to lukier składniowy nad useMemo: useCallback(fn, deps) jest równoważny useMemo(() => fn, deps). Ta składnia istnieje tylko dla czytelności — aby deweloper wyraźnie widział, że memoizowana jest właśnie funkcja, a nie wartość. Różnicy w wydajności między useCallback a useMemo z funkcją nie ma — generują ten sam kod.
| Hook | Memoizuje | Składnia | Zastosowanie |
|---|---|---|---|
| useCallback | Funkcję (referencję) | useCallback(fn, deps) | Callbacki dla komponentów potomnych |
| useMemo | Wynik obliczeń | useMemo(() => value, deps) | Kosztowne obliczenia, memoizacja obiektów |
// To jest równoważne:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);
Najczęstszym błędem jest bezsensowne owijanie wszystkich funkcji w useCallback bez React.memo na komponentach potomnych. Jeśli komponent potomny nie jest owinięty w React.memo, i tak będzie rerenderowany przy każdym renderze rodzica, niezależnie od tego, czy zmienia się referencja callbacka, czy nie. useCallback bez React.memo — to koszty bez korzyści.
// ❌ Bezużyteczne: brak React.memo na child
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Child nadal jest rerenderowany bez React.memo
// ❌ Stale closure: brakująca zależność
const handle = useCallback(() => {
console.log(count); // count zawsze wynosi 0 — stale closure!
}, []);
// ✅ Poprawnie: uwzględnij zależności
const handle = useCallback(() => {
console.log(count);
}, [count]);
Problem stale closure w useCallback rozwiązuje się, podając wszystkie używane zmienne w tablicy zależności. eslint-plugin-react-hooks z exhaustive-deps automatycznie sprawdza, czy wszystkie zmienne z ciała callbacka są obecne w tablicy. Jeśli callback używa setState, który nie zmienia się między renderami, można bezpiecznie umieścić go w deps — React gwarantuje stabilność setState.
Najczęściej zadawane pytania
Nie. useCallback ma sens tylko w trzech przypadkach: komponent potomny jest owinięty w React.memo, funkcja jest używana w tablicy zależności useEffect lub funkcja jest przekazywana do custom hooka zależnego od reference equality. W pozostałych przypadkach useCallback dodaje narzut bez korzyści. Zespół React zaleca najpierw pisać bez optymalizacji i dodawać je na podstawie profilowania.
Dla prostych komponentów — nowa funkcja przy każdym renderze jest nieco szybsza, ponieważ useCallback zużywa zasoby na porównywanie zależności i alokację tablicy. Dla komponentów z głębokimi drzewami React.memo — useCallback wygrywa, zapobiegając rerenderowi tysięcy elementów potomnych. Mierz i porównuj, a nie zgaduj — używaj React DevTools Profiler do obiektywnej oceny.
Tak, useCallback działa z funkcjami async dokładnie tak samo jak z synchronicznymi. Hook memoizuje samą funkcję, a wynik (Promise) jest zwracany za każdym razem przy wywołaniu. Funkcja asynchroniczna wewnątrz useCallback — to powszechny wzorzec dla stabilnych callbacków ładowania danych, które są używane w useEffect: const fetchData = useCallback(async (id) => {...}, []).
Użyj React DevTools Profiler — pokaże, które komponenty są rerenderowane i dlaczego. Do sprawdzenia programowego dodaj console.log lub useWhyDidYouUpdate — bibliotekę, która loguje przyczynę rerendera. Główne przyczyny: zmienił się prop (w tym referencja callbacka), zmienił się state lub kontekst. Jeśli useCallback nie pomaga — sprawdź, czy wszystkie zależności są podane poprawnie.
Nawet bez React.memo useCallback może być przydatny w połączeniu z useMemo dla value kontekstu. Jeśli przekazujesz obiekt z funkcjami do Context.Provider, owiń tworzenie obiektu w useMemo, a każdą funkcję — w useCallback. Zapobiegnie to rerenderowi wszystkich konsumentów kontekstu przy zmianie jednej z funkcji. Ale przy bezpośrednim przekazywaniu callbacków w propsy bez React.memo useCallback nie przynosi korzyści.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również