PassFront
  1. PassFront
  2. База знаний
  3. Хуки

Какие особенности зависимостей useCallback важно учитывать на практике и почему?

Хуки

Зависимости useCallback определяют, когда функция пересоздаётся. Ошибки здесь дают либо бесполезную мемоизацию, либо баги с устаревшими данными.

Полнота обязательна

Всё реактивное, что использует функция (props, state, другие функции компонента), должно быть в deps:

const buy = useCallback(() => {
  order(productId, qty);
}, [productId]);          // ❌ qty пропущен → всегда старое количество

Ссылка будет стабильной, но замыкание — устаревшим (stale closure). ESLint exhaustive-deps ловит; подавлять — значит прятать баг.

Нестабильные зависимости обнуляют смысл

const options = { currency: 'RUB' };            // новый объект каждый рендер
const buy = useCallback(() => order(options), [options]); // новая функция каждый рендер

Зависимости сами должны быть стабильны: примитивы, useMemo-объекты, стабильные колбэки. Иначе useCallback лишь добавляет накладные расходы.

Что можно не указывать

Сеттеры useState, dispatch из useReducer, ref-объект, значения из модульной области — они стабильны по определению.

Как честно сократить deps

const increment = useCallback(() => setCount((c) => c + 1), []); // без count

Функциональный сеттер убирает state из зависимостей. Для «свежего» значения без пересоздания функции — ref (latestRef.current), обновляемый в эффекте.

Смысл зависимостей — не только оптимизация

deps задают, как долго живёт ссылка: от этого зависят пропуски рендеров у memo-детей и перезапуски эффектов, где функция стоит в deps. Слишком широкие deps — мемоизация не работает; слишком узкие — работает с неверными данными.

Коротко для собеседования

Зависимости useCallback должны быть полными (иначе stale closure) и стабильными (иначе функция пересоздаётся каждый рендер и мемоизация бессмысленна). Сеттеры/dispatch/ref указывать не нужно; сокращать deps правильно через функциональные сеттеры или ref, а не отключением линтера.

Другие вопросы раздела