Зачем нужны deps в useCallback?
Хуки
deps в useCallback отвечают на вопрос: когда пересоздать мемоизированную функцию. Без правильных deps либо ломается стабильность (цель хука), либо функция работает с устаревшими данными.
Механика
const handleBuy = useCallback(() => {
submitOrder(productId, quantity);
}, [productId, quantity]);- Пока deps не изменились (Object.is поэлементно) — useCallback возвращает ту же ссылку, что и раньше.
- Изменились — возвращается новая функция, «замкнувшая» свежие значения.
Зачем нужны — двойная роль
Актуальность замыкания. Функция «фотографирует» переменные в момент создания. Если quantity нет в deps, обработчик навсегда останется с первоначальным значением (stale closure) — классический баг:
const handleBuy = useCallback(() => {
submitOrder(productId, quantity); // quantity всегда 1
}, [productId]); // ❌ quantity пропущенОсмысленная стабильность. deps определяют, как долго живёт ссылка: пустой массив — навсегда стабильна; [page] — обновляется при смене страницы. От этого зависит, когда перерендерятся memo-дети и перезапустятся эффекты, где функция в зависимостях.
Практические правила
- Все реактивные значения из тела функции — в deps (ESLint exhaustive-deps).
- Сеттеры useState/dispatch стабильны — можно не указывать; функциональные сеттеры (
setX((x) => ...)) позволяют убрать state из deps. - Значения-объекты в deps сами должны быть стабильны (useMemo), иначе useCallback бесполезен.
- Пустые deps «ради стабильности» при реальных зависимостях — это баг, а не оптимизация.
Коротко для собеседования
deps в useCallback управляют пересозданием функции: неизменные deps — старая ссылка (стабильность для memo-детей и эффектов), изменившиеся — новая функция со свежим замыканием. Пропуск зависимостей даёт stale closure; сокращать deps правильно через функциональные сеттеры, а не замалчиванием.