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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође