Может ли useCallback быть нужен для любого React-компонента или есть особенности?
Что проверяют
Понимаете ли вы, что useCallback ничего не ускоряет сам по себе, а только сохраняет ссылку на функцию между рендерами. Интервьюеру важно услышать, когда эта стабильная ссылка кому-то нужна. Следом обычно спрашивают, чем useCallback отличается от useMemo и что меняет React Compiler.
Как отвечать
Начните с механики: функция внутри компонента создаётся заново на каждом рендере, а useCallback(fn, deps) возвращает прежний экземпляр, пока не изменились зависимости. Сам колбэк при этом всё равно создаётся, React просто отбрасывает новую версию. Поэтому хук оправдан, только когда стабильность ссылки на что-то влияет:
- дочерний компонент в
React.memo: без стабильной ссылки поверхностное сравнение пропсов проваливается, и мемоизация ребёнка не работает; - зависимость другого хука: функция в массиве зависимостей
useEffectилиuseMemoбезuseCallbackперезапускает эффект на каждом рендере; - кастомный хук: возвращаемые из него функции лучше стабилизировать, потому что вы не знаете, куда их передаст потребитель;
- длинные списки: колбэк уходит в сотни мемоизированных строк, и лишние рендеры реально видны в React DevTools Profiler.
Если функция уходит в обычный <button onClick> или в немемоизированный компонент, useCallback только добавляет сравнение зависимостей и шум в коде. Упомяните функциональное обновление setCount(c => c + 1): оно позволяет оставить массив зависимостей пустым без риска устаревшего замыкания. И скажите про React Compiler: он расставляет мемоизацию компонентов и колбэков на этапе сборки, так что в проекте с ним ручной useCallback нужен в редких случаях.
Где спотыкаются
Оборачивают в useCallback каждую функцию «на всякий случай». Выигрыша нет, а читать и поддерживать массивы зависимостей становится тяжелее.
Забывают, что без React.memo у ребёнка стабильная ссылка ничего не даёт: компонент всё равно перерендерится вместе с родителем.
Пропускают зависимости, чтобы функция «не менялась». Получается устаревшее замыкание, и колбэк видит старый state. Правило react-hooks/exhaustive-deps из eslint-plugin-react-hooks ловит это сразу.