useCallback — это просто useMemo для функций

#react#hooks#performance

Вот вам мысль, которая перевернула мое понимание хуков: useCallback(fn, deps) — это буквально useMemo(() => fn, deps). Да, технически React так не реализован, но концептуально это один и тот же механизм кеширования, просто примененный к разным типам значений.

Почему это важно

Когда я только начал работать с хуками, useCallback казался мне какой-то магией. Зачем отдельный хук для функций, если есть useMemo? Оказывается, никакой магии:

// Эти два вызова эквивалентны
const memoizedFn = useCallback(() => doSomething(a, b), [a, b]);
const memoizedFn = useMemo(() => () => doSomething(a, b), [a, b]);

Разница лишь в том, что useMemo кеширует результат выполнения функции, а useCallback — саму функцию. Но поскольку в JavaScript функции — это объекты первого класса (то есть обычные значения), useCallback становится просто частным случаем useMemo.

Как это работает под капотом

Любопытно, что в исходниках React useCallback не реализован как обертка над useMemo. Оба хука используют общую кеширующую инфраструктуру, но имеют разные реализации. Вот как это примерно выглядит:

function useCallback(callback, deps) {
  return mountMemo(() => callback, deps);
}

function useMemo(create, deps) {
  return mountMemo(create, deps);
}

На практике это означает, что нет перформанс-разницы между использованием useCallback и useMemo для функций. Выбор между ними — вопрос читаемости кода.

Когда это знание полезно

  1. Дебунсинг и троттлинг: если вам нужно закешировать функцию с задержкой, проще использовать useMemo:

    const debouncedFn = useMemo(
      () => debounce(() => fetchData(query), 300),
      [query]
    );
    
  2. Мемоизация сложных объектов: когда нужно кешировать не только функции, но и другие значения:

    const config = useMemo(
      () => ({
        handler: () => console.log(value),
        timeout: 2000,
      }),
      [value]
    );
    
  3. Оптимизация рендеров: если компонент принимает и функции, и другие значения, useMemo унифицирует логику:

    const optimizedProps = useMemo(
      () => ({
        onClick: () => setCount(c => c + 1),
        theme: darkMode ? 'dark' : 'light',
      }),
      [darkMode]
    );
    

Где это может сломаться

Главный подводный камень — излишняя мемоизация. Не стоит оборачивать в useCallback/useMemo каждую функцию. Вот признаки, что вы переборщили:

В моей практике оптимально использовать эти хуки только когда:

  1. Функция передается в React.memo компонент
  2. Функция используется в эффектах/колбэках других хуков
  3. Создание функции действительно тяжелое (например, содержит сложные вычисления)

Что попробовать

Если вы никогда не сравнивали useCallback и useMemo, попробуйте:

  1. Заменить несколько useCallback на useMemo в реальном проекте
  2. Запустить бенчмарки (например, через React DevTools)
  3. Проверить, изменилось ли что-то в поведении

Скорее всего, разницы не будет — но понимание механизмов React станет глубже. А это всегда полезно, когда нужно отладить странное поведение или оптимизировать критичный участок кода.


Источник: https://www.reddit.com/r/reactjs/comments/1u1t7af/til_usecallback_usememo_returning_a_fn/