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 кожне оголошення функції всередині компонента створює новий об'єкт функції при кожному рендері. Для примітивів це непомітно, але при передачі таких колбеків у дочірні компоненти, оптимізовані через 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 зберігає замикання, створене в момент першого рендера, і повертає його при наступних рендерах, поки залежності залишаються незмінними. Всередині fiber-вузла кожен виклик useCallback створює вузол у зв'язаному списку хуків, де зберігаються попередні залежності та мемоізоване значення.

Процес порівняння залежностей виконується строго через Object.is — це поверхневе порівняння без глибокої перевірки об'єктів або масивів. Якщо в залежностях об'єкт або масив, нове посилання при кожному рендері буде вважатися зміною. Тому в масиві залежностей потрібно передавати примітивні значення або стабільні посилання (наприклад, з useRef або useMemo).

За даними React Core Team — Optimization Guide (2024), вартість мемоізації включає три компоненти: алокацію масиву залежностей на кожен рендер, обхід і порівняння елементів через Object.is і потенційний overhead від збирання сміття при перестворенні. Для компонента з сотнями useCallback-обгорток це може стати помітним — тому селективність у використанні хука критична.

СценарійБез useCallbackЗ useCallback
Створення функціїНова на кожен рендерТа ж при стабільних deps
Передача в React.memoДитина ререндеритьсяДитина не ререндериться
В масиві useEffectЕфект перезапускаєтьсяЕфект стабільний
OverheadМінімальнийПорівняння залежностей + пам'ять

useCallback та продуктивність

Широко поширений міф, що useCallback автоматично покращує продуктивність. Насправді, в ізоляції (без React.memo) useCallback навіть трохи сповільнює додаток через cost порівняння залежностей. Реальну користь хук приносить тільки в трьох сценаріях: запобігання ререндерам React.memo-компонентів, стабільність колбеків у useEffect і передача в кастомні хуки, які залежать від reference equality.

Правило просте: поки не виявиш проблему з продуктивністю через React DevTools Profiler — не використовуй useCallback. React-команда неодноразово підкреслювала, що попередня оптимізація — корінь зла. Спочатку напиши чистий код без мемоізації, виміряй, знайди bottleneck у профілювальнику і тільки потім додавай 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 і приймає колбек-функцію як проп, без useCallback дочірній компонент буде перемальовуватися при кожному рендері батька, навіть якщо його власні дані не змінилися. useCallback стабілізує посилання, і React.memo зможе коректно пропустити ререндер.

Другий сценарій — useEffect з колбеком у залежностях. Якщо функція передається в масив залежностей useEffect, кожне нове посилання буде перезапускати ефект. useCallback гарантує, що посилання стабільне, і ефект виконується тільки при зміні реальних даних, а не при кожному рендері. Це особливо важливо для підписок і запитів.

  • React.memo діти — запобігає ререндеру мемоізованих дочірніх компонентів при передачі колбеків.
  • useEffect залежності — стабілізує функцію в масиві залежностей ефекту, запобігаючи зайвим перезапускам.
  • Кастомні хуки — якщо хук приймає колбек і залежить від його reference equality, useCallback обов'язковий.
  • Context value — якщо в value контексту передається функція, 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 vs 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)Колбеки для дочірніх компонентів
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, він все одно буде ререндеритися при кожному рендері батька, незалежно від того, чи змінюється посилання на колбек. useCallback без React.memo — це витрати без вигоди.

  • useCallback без React.memo — дочірній компонент все одно ререндериться; оптимізація безглузда.
  • Забуті залежності — якщо всередині колбека використовується змінна, не вказана в deps, колбек містить stale closure.
  • Надлишкова мемоізація — обгортання кожної функції, включаючи тривіальні onClick з console.log.
  • Об'єкти та масиви в залежностях — нове посилання при кожному рендері вважається зміною, мемоізація марна.
jsx
// ❌ Марно: немає React.memo на дитині
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 автоматично перевіряє, що всі змінні з тіла колбека присутні в масиві. Якщо колбек використовує 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 — поширений патерн для стабільних колбеків завантаження даних, які використовуються в useEffect: const fetchData = useCallback(async (id) => {...}, []).

Як налагодити проблему з useCallback та React.memo?

Використовуй React DevTools Profiler — він покаже, які компоненти ререндеряться і чому. Для програмної перевірки додай console.log або useWhyDidYouUpdate — бібліотеку, яка логує причину ререндера. Основні причини: змінився проп (включаючи колбек-посилання), змінився state або контекст. Якщо useCallback не допомагає — перевір, що всі залежності вказані коректно.

Як передати useCallback у дочірній компонент без React.memo?

Навіть без React.memo useCallback може бути корисним у комбінації з useMemo для value контексту. Якщо ти передаєш об'єкт з функціями в Context.Provider, оберни створення об'єкта в useMemo, а кожну функцію — в useCallback. Це запобіжить ререндеру всіх споживачів контексту при зміні однієї з функцій. Але для прямої передачі колбеків у пропси без React.memo користі від useCallback немає.

Підсумки

  • useCallback — хук для мемоізації функції, що повертає стабільне посилання до зміни залежностей.
  • React.memo synergy — useCallback ефективний тільки в парі з React.memo на дочірніх компонентах.
  • Порівняння залежностей — через Object.is; об'єкти/масиви в deps ламають мемоізацію.
  • Вимірюй, не гадай — додавай useCallback тільки після виявлення bottleneck через профілювальник.
  • Stable closure — включай всі використовувані змінні в масив залежностей, інакше — stale closure.
  • useCallback vs useMemo — useCallback мемоізує функцію, useMemo — результат обчислення.
  • Надлишковість — понад 90% випадків використання useCallback у реальних проектах — premature optimization.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також