Вот вам мысль, которая перевернула мое понимание хуков: 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 для функций. Выбор между ними — вопрос читаемости кода.
Когда это знание полезно
-
Дебунсинг и троттлинг: если вам нужно закешировать функцию с задержкой, проще использовать
useMemo:const debouncedFn = useMemo( () => debounce(() => fetchData(query), 300), [query] ); -
Мемоизация сложных объектов: когда нужно кешировать не только функции, но и другие значения:
const config = useMemo( () => ({ handler: () => console.log(value), timeout: 2000, }), [value] ); -
Оптимизация рендеров: если компонент принимает и функции, и другие значения,
useMemoунифицирует логику:const optimizedProps = useMemo( () => ({ onClick: () => setCount(c => c + 1), theme: darkMode ? 'dark' : 'light', }), [darkMode] );
Где это может сломаться
Главный подводный камень — излишняя мемоизация. Не стоит оборачивать в useCallback/useMemo каждую функцию. Вот признаки, что вы переборщили:
- Кешируете функции, которые никогда не передаются в дочерние компоненты
- Массивы зависимостей становятся слишком сложными для отслеживания
- Профилировщик не показывает проблем с ререндерами
В моей практике оптимально использовать эти хуки только когда:
- Функция передается в
React.memoкомпонент - Функция используется в эффектах/колбэках других хуков
- Создание функции действительно тяжелое (например, содержит сложные вычисления)
Что попробовать
Если вы никогда не сравнивали useCallback и useMemo, попробуйте:
- Заменить несколько
useCallbackнаuseMemoв реальном проекте - Запустить бенчмарки (например, через React DevTools)
- Проверить, изменилось ли что-то в поведении
Скорее всего, разницы не будет — но понимание механизмов React станет глубже. А это всегда полезно, когда нужно отладить странное поведение или оптимизировать критичный участок кода.
Источник: https://www.reddit.com/r/reactjs/comments/1u1t7af/til_usecallback_usememo_returning_a_fn/