← Назад к вопросам

В чем проблема использования 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 мемоизировать имеет смысл

Рекомендации

  1. Не оптимизируй раньше времени - сначала профилируй
  2. Используй React DevTools Profiler - найди реальные bottleneck'и
  3. Начни с простого кода - сложность имеет стоимость
  4. Рассмотри архитектуру - часто проблема не в оптимизации, а в дизайне
  5. Профилируй результат - убедись, что оптимизация работает
  6. Используй Suspense/Transitions вместо useCallback - это современный подход

Правило большого пальца

Если ты не можешь объяснить, почему нужна эта оптимизация в двух предложениях — не делай её.

В чем проблема использования useCallback, useMemo, React memo? | PrepBro