Урок 06

useCallback: мемоизация функций

useCallback возвращает мемоизированную версию функции, которая меняется только при изменении зависимостей.

Синтаксис

const fn = useCallback(() => {
    doSomething(a, b);
}, [a, b]);

Без зависимостей — функция создаётся один раз:

const fn = useCallback(() => {
    console.log('один раз');
}, []);

Зачем нужен

В React функции создаются заново при каждом рендере. Если функция передаётся в дочерний компонент как пропс, дочерний компонент перерисовывается, даже если логика не изменилась.

function Parent() {
    const handleClick = () => console.log('click');

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

При каждом рендере Parent создаёт новую handleClick, и Child обновляется.

С useCallback:

function Parent() {
    const handleClick = useCallback(() => console.log('click'), []);

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

Теперь функция стабильна между рендерами.

Когда работает

useCallback полезен, если дочерний компонент мемоизирован через React.memo:

const Child = React.memo(function Child({ onClick }: { onClick: () => void }) {
    console.log('Child render');
    return <button onClick={onClick}>OK</button>;
});

Без React.memo useCallback не даёт эффекта: Child всё равно перерисовывается при перерисовке родителя.

В зависимостях useEffect

Если функция используется в useEffect, её тоже нужно стабилизировать:

const loadData = useCallback(async () => {
    const res = await fetch(`/api/users/${userId}`);
    setUser(await res.json());
}, [userId]);

useEffect(() => {
    loadData();
}, [loadData]);

Без useCallback функция меняется каждый рендер, и эффект сработает бесконечно.

Функциональное обновление

Если функция использует предыдущее состояние — используй функциональную форму, чтобы уменьшить зависимости:

// Зависит от count
const increment = useCallback(() => {
    setCount(count + 1);
}, [count]);

// Не зависит от count
const increment = useCallback(() => {
    setCount((c) => c + 1);
}, []);

useCallback vs useMemo

  • useCallback(fn, deps) эквивалентно useMemo(() => fn, deps)
  • useCallback — для функций
  • useMemo — для значений

Не оптимизируй преждевременно

Оборачивать в useCallback каждую функцию — плохая идея. Это усложняет код и добавляет работу. Используй только там, где это реально нужно: для мемоизированных компонентов и зависимостей эффектов.

Итоги

  • useCallback мемоизирует функцию
  • Полезен с React.memo и в зависимостях
  • Без мемоизации дочернего компонента не даёт эффекта
  • Функциональное обновление уменьшает зависимости
  • Не оборачивай всё подряд