Что нужно useCallback для избежания лишних рендеров?
Хуки
Сам по себе useCallback лишних рендеров не убирает — для этого ему нужны три условия, работающие вместе.
Условие 1: React.memo на компоненте-получателе
Рендер пропускает не useCallback, а React.memo. Стабильная функция без memo-обёртки у ребёнка ничего не даёт — он перерендерится вместе с родителем безусловно:
const List = React.memo(ListInner); // без этого useCallback у родителя бессмысленУсловие 2: правильные зависимости
const handleSelect = useCallback((id) => {
onSelect(id, mode);
}, [onSelect, mode]); // все используемые значения — в deps- Лишние/нестабильные зависимости → функция пересоздаётся каждый рендер → memo снова ломается.
- Пропущенные зависимости → ссылка стабильна, но замыкание устарело: обработчик работает со старыми значениями. Это уже баг корректности, не производительности.
Условие 3: стабильность ВСЕХ остальных props
Memo сравнивает каждый проп; один нестабильный — и сравнение провалено:
<List
onSelect={handleSelect} // стабильно (useCallback)
style={{ gap: 8 }} // ломает всё: новый объект каждый рендер
/>Инлайн-объекты — в useMemo (или константу вне компонента), children тоже учитывать.
Дополнительно
- Цепочка работает и для зависимостей эффектов: стабильный колбэк в deps useEffect у ребёнка предотвращает лишние перезапуски эффекта.
- Проверять результат профайлером: «выглядит мемоизированным» ≠ «не рендерится».
Коротко для собеседования
Чтобы useCallback реально убирал рендеры, нужно: React.memo на получателе (именно он пропускает рендер), корректные полные зависимости (иначе либо нестабильность, либо stale closure) и стабильность всех остальных props (один инлайн-объект ломает сравнение). useCallback — только звено цепочки.