← Назад к вопросам
В чем проблема использования useCallback, useMemo, React memo?
2.0 Middle🔥 271 комментариев
#React#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
В чем проблема использования useCallback, useMemo, React.memo
Критический момент
Эти оптимизации необходимы, но их чрезмерное и неправильное использование создаёт проблемы, а не решает их.
1. Проблема: Слепая оптимизация без профилирования
Антипаттерн
// ПЛОХО: Оптимизируем всё подряд без причины
const MyComponent = memo(() => {
const value = useMemo(() => ({a: 1}), []);
const handleClick = useCallback(() => {}, []);
return <div>{value.a}</div>;
});
Проблемы:
- useCallback и useMemo имеют свою стоимость (память, CPU)
- Без реального bottleneck это просто замедляет код
- Harder для чтения и поддержки
Правильный подход
// Сначала измери
console.time('render');
// ... компонент
console.timeEnd('render');
// Или используй React DevTools Profiler
// Только потом оптимизируй узкие места!
2. Проблема: Стоимость useCallback и useMemo
Закрытие и зависимости
// Ошибка: useCallback со сложным массивом зависимостей
const handleClick = useCallback(() => {
console.log(user, profile, settings, theme, data);
}, [user, profile, settings, theme, data]); // 5 зависимостей!
// При каждом изменении любой из них функция пересоздаётся
// Это часто происходит, поэтому useCallback НЕ помогает
Стоимость в памяти
// Каждый useCallback/useMemo
const value = useMemo(() => {
return complexCalculation();
}, [deps]);
// Требует хранить:
// - Предыдущее значение
// - Массив зависимостей
// - Замыкание функции
// Это БОЛЬШЕ памяти, чем пересчёт!
3. Проблема: React.memo и поверхностное сравнение
Props всё равно меняются
const Child = memo(({ onClick, data }) => {
return <button onClick={onClick}>{data.name}</button>;
});
function Parent() {
// Проблема: onClick создаётся заново каждый раз
return <Child onClick={() => {}} data={{name: 'test'}} />;
}
// React.memo сравнит:
// onClick: Function !== Function -> пересчёт!
// data: Object !== Object -> пересчёт!
Правильное использование
const Child = memo(({ onClick, data }) => {
return <button onClick={onClick}>{data.name}</button>;
});
function Parent() {
const handleClick = useCallback(() => {}, []);
const data = useMemo(() => ({name: 'test'}), []);
return <Child onClick={handleClick} data={data} />;
}
// Теперь мемоизация работает
// Но добавили 2 доп. хука — больше сложности!
4. Проблема: Стоимость сравнения зависимостей
Массив зависимостей пересчитывается каждый раз
const value = useMemo(() => {
return { id: userId };
}, [userId]); // Каждый рендер проверяет, изменился ли userId
// React.js должна:
// 1. Сравнить userId с предыдущим значением
// 2. Решить, пересчитать ли значение
// Это ТОЖЕ имеет стоимость!
Для простых вычислений это может быть медленнее
// ПЛОХО: useMemo на простом вычислении
const doubled = useMemo(() => value * 2, [value]); // Зачем?
// ХОРОШО: просто вычисли
const doubled = value * 2; // На порядки быстрее
5. Проблема: Забывчивость в зависимостях
ESLint подловит, но это выглядит как шум
// eslint-disable-next-line react-hooks/exhaustive-deps
const handleClick = useCallback(() => {
doSomething(user); // user меняется, но не в зависимостях
}, []); // Баг!
// Каждый раз добавляешь "disable" вместо пересмотра логики
Проблема с ref
const handleClick = useCallback(() => {
inputRef.current.focus();
}, []); // ref не нужен в зависимостях
// Это работает, но выглядит "магично"
6. Проблема: Усложнение кода
Простой компонент становится сложным
// ДО: Читаемо и просто
function UserCard({ user }) {
return <div onClick={() => alert(user.name)}>{user.email}</div>;
}
// ПОСЛЕ: Запутанно
const UserCard = memo(({ user }) => {
const handleClick = useCallback(() => {
alert(user.name);
}, [user]);
return <div onClick={handleClick}>{user.email}</div>;
});
7. Проблема: Несовместимость с Concurrent Rendering
// useTransition часто работает лучше чем useCallback
const [isPending, startTransition] = useTransition();
const handleClick = () => {
startTransition(() => {
// React может прервать это, если нужно
updateSearch(value);
});
};
// useCallback не знает о приоритетах
const handleClick = useCallback(() => {
updateSearch(value); // Всегда имеет один приоритет
}, [value]);
Когда действительно нужны эти оптимизации
1. Тяжёлые вычисления
const expensiveValue = useMemo(() => {
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i);
}
return result;
}, [inputData]);
// Имеет смысл, если это действительно затратно
2. Передача callbacks в оптимизированные дочерние компоненты
// Child оптимизирован и чувствителен к изменениям props
const Child = memo(({ onApply }) => {
return <button onClick={onApply}>Apply</button>;
});
function Parent() {
// Тогда имеет смысл мемоизировать callback
const handleApply = useCallback(() => {
applyChanges();
}, []);
return <Child onApply={handleApply} />;
}
3. Списки и таблицы с большим количеством элементов
const HugeList = memo(({ items, onSelect }) => {
return items.map(item => (
<Item key={item.id} data={item} onSelect={onSelect} />
));
});
// onSelect мемоизировать имеет смысл
Рекомендации
- Не оптимизируй раньше времени - сначала профилируй
- Используй React DevTools Profiler - найди реальные bottleneck'и
- Начни с простого кода - сложность имеет стоимость
- Рассмотри архитектуру - часто проблема не в оптимизации, а в дизайне
- Профилируй результат - убедись, что оптимизация работает
- Используй Suspense/Transitions вместо useCallback - это современный подход
Правило большого пальца
Если ты не можешь объяснить, почему нужна эта оптимизация в двух предложениях — не делай её.