useCallback — React хук, който връща мемоизирана версия на функцията, която не се променя между рендърите, докато не се променят нейните зависимости. За разлика от обичайното деклариране на функция вътре в компонента (което създава нова функция при всеки рендър), useCallback стабилизира референцията към функцията, което предотвратява ненужните ре-рендъри на дъщерни компоненти, оптимизирани чрез React.memo. Според React Documentation (2025), useCallback е полезен само в комбинация с React.memo или с хукове, които зависят от стабилна референция.
Най-важното
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 само ще забавя приложението. Оптимизацията трябва да бъде измерима, а не интуитивна.
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 автоматично подобрява производителността. В действителност, изолирано (без React.memo) useCallback дори леко забавя приложението поради цената на сравняването на зависимостите. Хукът носи реална полза само в три сценария: предотвратяване на ре-рендъри на React.memo компоненти, стабилност на callback-ите в useEffect и предаване на персонализирани хукове, които зависят от reference equality.
Правилото е просто: докато не откриеш проблем с производителността чрез React DevTools Profiler — не използвай useCallback. Екипът на React многократно е подчертавал, че преждевременната оптимизация е коренът на злото. Първо напиши чист код без мемоизация, измери, намери тясното място в профилъра и едва тогава добави useCallback там, където наистина е необходимо.
// Измерима оптимизация: 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 са оправдани. Ако дъщерният компонент е лек — ре-рендърът е по-евтин от сравняването на зависимостите.
Първият сценарий — React.memo. Ако дъщерният компонент е обвит в React.memo и приема callback функция като prop, без useCallback дъщерният компонент ще се прерисува при всеки рендър на родителя, дори ако собствените му данни не са се променили. useCallback стабилизира референцията и React.memo може правилно да пропусне ре-рендъра.
Вторият сценарий — useEffect с callback в зависимостите. Ако функцията се предава в масива от зависимости на useEffect, всяка нова референция ще рестартира ефекта. useCallback гарантира, че референцията е стабилна, и ефектът се изпълнява само при промяна на реалните данни, а не при всеки рендър. Това е особено важно за абонаменти и заявки.
// 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 мемоизира функцията: 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) | Скъпи изчисления, мемоизация на обекти |
// Тези са еквивалентни:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);
Най-честата грешка — безсмисленото обвиване на всички функции в useCallback без React.memo върху дъщерните компоненти. Ако дъщерният компонент не е обвит в React.memo, той ще се ре-рендърва при всеки рендър на родителя, независимо дали референцията на callback-а се променя или не. useCallback без React.memo — разходи без полза.
// ❌ Безполезно: без 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 има смисъл само в три случая: дъщерният компонент е обвит в React.memo, функцията се използва в масива от зависимости на useEffect или функцията се предава на персонализиран хук, зависещ от reference equality. В останалите случаи useCallback добавя overhead без полза. Екипът на React препоръчва първо да пишете без оптимизации и да ги добавяте по резултатите от профилирането.
За прости компоненти новата функция при всеки рендър е леко по-бърза, тъй като useCallback харчи ресурси за сравняване на зависимостите и заделяне на масив. За компоненти с дълбоки дървета React.memo useCallback печели, като предотвратява ре-рендъра на хиляди дъщерни елементи. Измервай и сравнявай, а не гадай — използвай React DevTools Profiler за обективна оценка.
Да, useCallback работи с async функции точно както със синхронните. Хукът мемоизира самата функция, а резултатът (Promise) се връща всеки път при извикването. Асинхронната функция вътре в useCallback е често срещан модел за стабилни callback-и за зареждане на данни, използвани в useEffect: const fetchData = useCallback(async (id) => {...}, []).
Използвай React DevTools Profiler — ще покаже кои компоненти се ре-рендърват и защо. За програмна проверка добави console.log или useWhyDidYouUpdate — библиотека, която записва причината за ре-рендъра. Основните причини: променил се е prop (включително референцията на callback-а), променил се е state или контекстът. Ако useCallback не помага — провери дали всички зависимости са посочени правилно.
Дори без React.memo, useCallback може да бъде полезен в комбинация с useMemo за value на контекста. Ако предаваш обект с функции на Context.Provider, обвий създаването на обекта в useMemo, а всяка функция — в useCallback. Това ще предотврати ре-рендъра на всички потребители на контекста при промяна на една от функциите. Но за директно предаване на callback-и в props без React.memo useCallback не носи полза.
Изводи
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също