useCallback — ключови понятия, хукът за мемоизация на функции в React

Автор: IT Sectr Публикувано: 2026-07-04 Време за четене: 9 мин

useCallback — React хук, който връща мемоизирана версия на функцията, която не се променя между рендърите, докато не се променят нейните зависимости. За разлика от обичайното деклариране на функция вътре в компонента (което създава нова функция при всеки рендър), useCallback стабилизира референцията към функцията, което предотвратява ненужните ре-рендъри на дъщерни компоненти, оптимизирани чрез React.memo. Според React Documentation (2025), useCallback е полезен само в комбинация с React.memo или с хукове, които зависят от стабилна референция.

Най-важното

  • useCallback — хук за мемоизация на функция, който запазва референцията между рендърите.
  • React.memo — useCallback е ефективен само в комбинация с React.memo за предотвратяване на ре-рендъри.
  • Зависимости — функцията се пресъздава само при промяна на посочените стойности в масива.
  • Стабилна референция — useCallback гарантира, че референцията към функцията няма да се промени без необходимост.
  • Не злоупотребявай — прекомерният useCallback без React.memo влошава производителността.

Какво е useCallback в React

useCallback — хук, добавен в React 16.8, който мемоизира функцията: връща същата референция, докато зависимостите не се променят. Без useCallback всяко деклариране на функция вътре в компонента създава нов обект функция при всеки рендър. За примитивите това е незабележимо, но при предаването на такива callback-и на дъщерни компоненти, оптимизирани чрез React.memo, всяка нова референция предизвиква ре-рендър на дъщерния компонент.

Синтактично useCallback е еквивалентен на useMemo за функция: useCallback(fn, deps) — това е съкращение за useMemo(() => fn, deps). React съхранява мемоизираната функция във вътрешното хранилище на fiber възела и сравнява зависимостите при всеки рендър. Ако зависимостите не са се променили (Object.is за всеки елемент), се връща предишната функция.

Според React Documentation — useCallback (2025), не е нужно да обвиваш всяка функция в useCallback. Хукът има своя цена: извикване на хука, сравняване на зависимостите и заделяне на памет за масива от зависимости. Ако компонентът е прост и няма дълбоки дървета с React.memo — useCallback само ще забавя приложението. Оптимизацията трябва да бъде измерима, а не интуитивна.

jsx
import { useCallback } from 'react';

function Parent() {
    const [count, setCount] = useState(0);

    // Стабилна референция — същата функция, докато deps не се променят
    const handleClick = useCallback(() => {
        setCount(prev => prev + 1);
    }, []);

    return <Child onClick={handleClick} />;
}

Как работи мемоизацията на функции

Мемоизацията в useCallback се основава на кеширане на резултата от извикването на функцията. React запазва closure-а, създаден в момента на първия рендър, и го връща при следващите рендъри, докато зависимостите остават непроменени. Вътре във fiber възела всяко извикване на useCallback създава възел в свързания списък на хуковете, където се съхраняват предишните зависимости и мемоизираната стойност.

Процесът на сравняване на зависимостите се извършва строго чрез Object.is — това е повърхностно сравнение, без задълбочена проверка на обекти или масиви. Ако в зависимостите има обект или масив, новата референция при всеки рендър ще бъде считана за промяна. Затова в масива от зависимости трябва да се предават примитивни стойности или стабилни референции (например от useRef или useMemo).

Според React Core Team — Optimization Guide (2024), цената на мемоизацията включва три компонента: заделяне на масива от зависимости при всеки рендър, обхождане и сравняване на елементите чрез Object.is и потенциален overhead от garbage collection при пресъздаването. За компонент със стотици useCallback обвивки това може да стане забележимо — затова селективността при използването на хука е от решаващо значение.

СценарийБез useCallbackС useCallback
Създаване на функцияНова при всеки рендърСъщата при стабилни deps
Предаване в React.memoДетето се ре-рендърваДетето не се ре-рендърва
В масива на useEffectЕфектът се рестартираЕфектът е стабилен
OverheadМинималенСравняване на зависимости + памет

useCallback и производителност

Разпространен е митът, че useCallback автоматично подобрява производителността. В действителност, изолирано (без React.memo) useCallback дори леко забавя приложението поради цената на сравняването на зависимостите. Хукът носи реална полза само в три сценария: предотвратяване на ре-рендъри на React.memo компоненти, стабилност на callback-ите в useEffect и предаване на персонализирани хукове, които зависят от reference equality.

Правилото е просто: докато не откриеш проблем с производителността чрез React DevTools Profiler — не използвай useCallback. Екипът на React многократно е подчертавал, че преждевременната оптимизация е коренът на злото. Първо напиши чист код без мемоизация, измери, намери тясното място в профилъра и едва тогава добави useCallback там, където наистина е необходимо.

jsx
// Измерима оптимизация: Child е обвит в React.memo
const Child = React.memo(({ onClick }) => {
    console.log('Child се ре-рендърва');
    return <button onClick={onClick}>Click</button>;
});

function Parent() {
    const handleClick = useCallback(() => {
        console.log('щракнато');
    }, []);
    return <Child onClick={handleClick} />;
}

Според Dan Abramov — Before You memo() (2024), повече от 90% от случаите на използване на useCallback в open-source проекти са излишни. Разработчиците обвиват всяка функция „за всеки случай", без да измерват ефекта. Алтернатива: ако дъщерният компонент е тежък и ре-рендърът му е скъп — React.memo + useCallback са оправдани. Ако дъщерният компонент е лек — ре-рендърът е по-евтин от сравняването на зависимостите.

Кога да използваш useCallback

Първият сценарий — React.memo. Ако дъщерният компонент е обвит в React.memo и приема callback функция като prop, без useCallback дъщерният компонент ще се прерисува при всеки рендър на родителя, дори ако собствените му данни не са се променили. useCallback стабилизира референцията и React.memo може правилно да пропусне ре-рендъра.

Вторият сценарий — useEffect с callback в зависимостите. Ако функцията се предава в масива от зависимости на useEffect, всяка нова референция ще рестартира ефекта. useCallback гарантира, че референцията е стабилна, и ефектът се изпълнява само при промяна на реалните данни, а не при всеки рендър. Това е особено важно за абонаменти и заявки.

  • React.memo children — предотвратява ре-рендъра на мемоизирани дъщерни компоненти при предаване на callback-и.
  • useEffect dependencies — стабилизира функцията в масива от зависимости на ефекта, предотвратявайки излишни рестартирания.
  • Персонализирани хукове — ако хукът приема callback и зависи от неговото reference equality, useCallback е задължителен.
  • Context value — ако във value на контекста се предава функция, useMemo с useCallback стабилизира референцията.
jsx
// useCallback за стабилна зависимост на useEffect
const fetchData = useCallback(async (id) => {
    const res = await fetch(`/api/${id}`);
    setData(res.data);
}, []); // стабилна референция, никога не се пресъздава

useEffect(() => {
    fetchData(props.id);
}, [props.id, fetchData]); // ефектът се изпълнява само при промяна на props.id

useCallback срещу useMemo

Основната разлика между useCallback и useMemo е какво мемоизира всеки от тях. useCallback мемоизира функцията: useCallback(fn, deps) връща fn (същата или предишната версия). useMemo мемоизира резултата от извикването на функцията: useMemo(() => computeExpensive(a, b), [a, b]) връща изчислената стойност, а не функцията.

Технически useCallback е синтактична захар над useMemo: useCallback(fn, deps) е еквивалентен на useMemo(() => fn, deps). Тази синтактика съществува само за четимост — за да вижда разработчикът ясно, че се мемоизира именно функцията, а не стойността. Няма разлика в производителността между useCallback и useMemo с функция — те генерират един и същ код.

ХукМемоизираСинтаксисУпотреба
useCallbackФункцията (референцията)useCallback(fn, deps)Callback-и за дъщерни компоненти
useMemoРезултата от изчислениетоuseMemo(() => value, deps)Скъпи изчисления, мемоизация на обекти
jsx
// Тези са еквивалентни:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);

Типични грешки с useCallback

Най-честата грешка — безсмисленото обвиване на всички функции в useCallback без React.memo върху дъщерните компоненти. Ако дъщерният компонент не е обвит в React.memo, той ще се ре-рендърва при всеки рендър на родителя, независимо дали референцията на callback-а се променя или не. useCallback без React.memo — разходи без полза.

  • useCallback без React.memo — дъщерният компонент пак се ре-рендърва; оптимизацията е безсмислена.
  • Забравени зависимости — ако вътре в callback-а се използва променлива, която не е посочена в deps, callback-ът съдържа stale closure.
  • Прекомерна мемоизация — обвиване на всяка функция, включително тривиални onClick с console.log.
  • Обекти и масиви в зависимостите — новата референция при всеки рендър се счита за промяна, мемоизацията е безполезна.
jsx
// ❌ Безполезно: без React.memo на child
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Child пак се ре-рендърва без React.memo

// ❌ Stale closure: липсва зависимост
const handle = useCallback(() => {
    console.log(count); // count винаги е 0 — stale closure!
}, []);

// ✅ Правилно: включи зависимостите
const handle = useCallback(() => {
    console.log(count);
}, [count]);

Проблемът stale closure в useCallback се решава чрез посочване на всички използвани променливи в масива от зависимости. eslint-plugin-react-hooks с exhaustive-deps автоматично проверява, че всички променливи от тялото на callback-а присъстват в масива. Ако callback-ът използва setState, който не се променя между рендърите, може безопасно да се посочи в deps — React гарантира стабилността на setState.

Често задавани въпроси

Трябва ли да обвивам всяка функция в useCallback?

Не. useCallback има смисъл само в три случая: дъщерният компонент е обвит в React.memo, функцията се използва в масива от зависимости на useEffect или функцията се предава на персонализиран хук, зависещ от reference equality. В останалите случаи useCallback добавя overhead без полза. Екипът на React препоръчва първо да пишете без оптимизации и да ги добавяте по резултатите от профилирането.

Кое е по-бързо — useCallback или нова функция при всеки рендър?

За прости компоненти новата функция при всеки рендър е леко по-бърза, тъй като useCallback харчи ресурси за сравняване на зависимостите и заделяне на масив. За компоненти с дълбоки дървета React.memo useCallback печели, като предотвратява ре-рендъра на хиляди дъщерни елементи. Измервай и сравнявай, а не гадай — използвай React DevTools Profiler за обективна оценка.

Мога ли да използвам useCallback с async функции?

Да, useCallback работи с async функции точно както със синхронните. Хукът мемоизира самата функция, а резултатът (Promise) се връща всеки път при извикването. Асинхронната функция вътре в useCallback е често срещан модел за стабилни callback-и за зареждане на данни, използвани в useEffect: const fetchData = useCallback(async (id) => {...}, []).

Как да отстраня проблема с useCallback и React.memo?

Използвай React DevTools Profiler — ще покаже кои компоненти се ре-рендърват и защо. За програмна проверка добави console.log или useWhyDidYouUpdate — библиотека, която записва причината за ре-рендъра. Основните причини: променил се е prop (включително референцията на callback-а), променил се е state или контекстът. Ако useCallback не помага — провери дали всички зависимости са посочени правилно.

Как да предам useCallback на дъщерен компонент без React.memo?

Дори без React.memo, useCallback може да бъде полезен в комбинация с useMemo за value на контекста. Ако предаваш обект с функции на Context.Provider, обвий създаването на обекта в useMemo, а всяка функция — в useCallback. Това ще предотврати ре-рендъра на всички потребители на контекста при промяна на една от функциите. Но за директно предаване на callback-и в props без React.memo useCallback не носи полза.

Изводи

  • useCallback — хук за мемоизация на функция, който връща стабилна референция до промяна на зависимостите.
  • React.memo synergy — useCallback е ефективен само в комбинация с React.memo върху дъщерни компоненти.
  • Сравняване на зависимости — чрез Object.is; обектите/масивите в deps развалят мемоизацията.
  • Измервай, не гадай — добавяй useCallback само след откриване на тясно място чрез профилъра.
  • Stable closure — включвай всички използвани променливи в масива от зависимости, иначе — stale closure.
  • useCallback срещу useMemo — useCallback мемоизира функцията, useMemo — резултата от изчислението.
  • Излишък — повече от 90% от случаите на използване на useCallback в реални проекти са преждевременна оптимизация.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също