useCallback — kluczowe pojęcia, hook memoizacji funkcji w React

Autor: IT Sectr Opublikowano: 2026-07-04 Czas czytania: 9 min

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 — hook do memoizacji funkcji, który zachowuje referencję między renderami.
  • React.memo — useCallback jest skuteczny tylko w połączeniu z React.memo w celu zapobiegania rerenderom.
  • Zależności — funkcja jest odtwarzana tylko przy zmianie wskazanych wartości w tablicy.
  • Stabilna referencja — useCallback gwarantuje, że referencja do funkcji nie zmieni się bez potrzeby.
  • Nie nadużywaj — nadmiarowe useCallback bez React.memo pogarsza wydajność.

Czym jest useCallback w React

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.

jsx
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} />;
}

Jak działa memoizacja funkcji

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.

ScenariuszBez useCallbackZ useCallback
Tworzenie funkcjiNowa przy każdym renderzeTa sama przy stabilnych deps
Przekazanie do React.memoDziecko jest rerenderowaneDziecko nie jest rerenderowane
W tablicy useEffectEfekt jest restartowanyEfekt jest stabilny
NarzutMinimalnyPorównanie zależności + pamięć

useCallback a wydajność

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.

jsx
// 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.

Kiedy używać useCallback

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ń.

  • React.memo children — zapobiega rerenderom zmemorizowanych komponentów potomnych przy przekazywaniu callbacków.
  • useEffect dependencies — stabilizuje funkcję w tablicy zależności efektu, zapobiegając zbędnym restartom.
  • Custom hooki — jeśli hook przyjmuje callback i zależy od jego reference equality, useCallback jest obowiązkowy.
  • Context value — jeśli w value kontekstu przekazywana jest funkcja, useMemo z useCallback stabilizują referencję.
jsx
// 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

useCallback vs useMemo

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.

HookMemoizujeSkładniaZastosowanie
useCallbackFunkcję (referencję)useCallback(fn, deps)Callbacki dla komponentów potomnych
useMemoWynik obliczeńuseMemo(() => value, deps)Kosztowne obliczenia, memoizacja obiektów
jsx
// To jest równoważne:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);

Typowe błędy z useCallback

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.

  • useCallback bez React.memo — komponent potomny i tak jest rerenderowany; optymalizacja jest bezsensowna.
  • Zapomniane zależności — jeśli wewnątrz callbacka używana jest zmienna nieuwzględniona w deps, callback zawiera stale closure.
  • Nadmiarowa memoizacja — owijanie każdej funkcji, w tym trywialnych onClick z console.log.
  • Obiekty i tablice w zależnościach — nowa referencja przy każdym renderze jest uznawana za zmianę, memoizacja jest bezużyteczna.
jsx
// ❌ 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

Czy trzeba owijać każdą funkcję w useCallback?

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.

Co jest szybsze — useCallback czy nowa funkcja przy każdym renderze?

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.

Czy można używać useCallback z funkcjami async?

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) => {...}, []).

Jak debugować problem z useCallback i React.memo?

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.

Jak przekazać useCallback do komponentu potomnego bez React.memo?

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

  • useCallback — hook do memoizacji funkcji, zwracający stabilną referencję do czasu zmiany zależności.
  • React.memo synergy — useCallback jest skuteczny tylko w połączeniu z React.memo na komponentach potomnych.
  • Porównanie zależności — przez Object.is; obiekty/tablice w deps psują memoizację.
  • Mierz, nie zgaduj — dodawaj useCallback tylko po wykryciu wąskiego gardła przez profiler.
  • Stable closure — uwzględniaj wszystkie używane zmienne w tablicy zależności, w przeciwnym razie — stale closure.
  • useCallback vs useMemo — useCallback memoizuje funkcję, useMemo — wynik obliczeń.
  • Nadmiarowość — ponad 90% przypadków użycia useCallback w rzeczywistych projektach to przedwczesna optymalizacja.

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.

Omów projekt

Przeczytaj również