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

Может ли useMemo заменить useCallback?

2.0 Middle🔥 211 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Может ли useMemo заменить useCallback

Это частый вопрос на интервью. Короткий ответ: технически да, но это не лучшая практика. Разберемся почему.

Базовое различие

useCallback:

const memoFunc = useCallback(() => {
  console.log(value);
}, [value]);
// Возвращает функцию

useMemo:

const memoFunc = useMemo(() => {
  return () => {
    console.log(value);
  };
}, [value]);
// Возвращает результат выполнения функции

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

Пример: замена useCallback на useMemo

// Вариант 1: useCallback (стандартный способ)
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <Child onClick={handleClick} />;
}

// Вариант 2: useMemo (можно, но странно)
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useMemo(() => {
    return () => {
      setCount(c => c + 1);
    };
  }, []);

  return <Child onClick={handleClick} />;
}

// Оба варианта работают одинаково!

Почему useMemo может заменить useCallback

Технически это работает, потому что:

  1. Оба хука используют одинаковый механизм мемоизации
  2. Оба сравнивают зависимости
  3. Оба кешируют результат

Почему это не лучшая практика

1. Намерение (Intent)

// ✅ Понятно: мемоизируем функцию
const handleClick = useCallback(() => { /* ... */ }, [deps]);

// ❌ Неясно: зачем мемоизировать функцию через useMemo?
const handleClick = useMemo(() => () => { /* ... */ }, [deps]);

Код - это прежде всего общение с другими разработчиками. useCallback явно говорит: "здесь функция".

2. Производительность

Теоретически useMemo медленнее, потому что дополнительно вызывает функцию при рендере:

// useCallback - быстрее
useCallback(() => {
  // функция
}, []);

// useMemo - медленнее (создает функцию, потом вызывает её)
useMemo(() => {
  return () => {
    // функция
  };
}, []);

Эта разница микроскопическая, но логически useCallback эффективнее.

3. Читаемость

// ❌ Сложно читать
const handleClick = useMemo(() => {
  return () => {
    doSomething();
  };
}, [deps]);

// ✅ Ясно
const handleClick = useCallback(() => {
  doSomething();
}, [deps]);

useMemo требует дополнительного уровня вложенности.

4. Опечатки

// ❌ Легко забыть return
const handleClick = useMemo(() => {
  () => { // Опечатка! Функция не вернется
    doSomething();
  };
}, [deps]);

console.log(handleClick); // undefined - ошибка!

// ✅ Невозможно ошибиться
const handleClick = useCallback(() => {
  doSomething();
}, [deps]);

Реальные примеры

Пример 1: Оптимизация child компонента

// ✅ ПРАВИЛЬНО - useCallback
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  // Child не будет re-render, если handleClick не изменилась
  return <Child onClick={handleClick} />;
}

// ❌ НЕПРАВИЛЬНО - useMemo для этого
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useMemo(() => {
    return () => {
      setCount(c => c + 1);
    };
  }, []);

  return <Child onClick={handleClick} />;
}

Пример 2: Dependency в useEffect

// ✅ ПРАВИЛЬНО - useCallback
function Component() {
  const [data, setData] = useState(null);
  const [id, setId] = useState(1);

  const fetchData = useCallback(() => {
    fetch(`/api/data/${id}`)
      .then(res => res.json())
      .then(setData);
  }, [id]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);
}

// ❌ СТРАННО - useMemo для этого
function Component() {
  const [data, setData] = useState(null);
  const [id, setId] = useState(1);

  const fetchData = useMemo(() => {
    return () => {
      fetch(`/api/data/${id}`)
        .then(res => res.json())
        .then(setData);
    };
  }, [id]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);
}

Когда useMemo может быть полезнее

Одна ситуация, где useMemo имеет смысл - когда функция нужна для инициализации другого хука:

function Component() {
  // useMemo, потому что нужно значение, не функция
  const config = useMemo(() => ({
    handler: () => { /* ... */ },
    options: { /* ... */ }
  }), [deps]);

  useCustomHook(config); // config меняется редко
}

Здесь мы мемоизируем объект, который содержит функцию, а не саму функцию.

Сравнительная таблица

АспектuseCallbackuseMemo для функции
ПредназначениеМемоизация функцийУниверсальная мемоизация
ПроизводительностьЛучшеМедленнее
ЧитаемостьЯснаяНеясная
Количество кодаМеньшеБольше
Вероятность ошибкиНизкаяВыше (забыть return)
Цель хукаЯвнаяСкрытая

Лучшие практики

// ✅ ХОРОШО - useCallback для функций
const handleClick = useCallback(() => {
  doSomething();
}, [deps]);

// ✅ ХОРОШО - useMemo для сложных вычислений
const sorted = useMemo(() => {
  return data.sort((a, b) => a - b);
}, [data]);

// ✅ ХОРОШО - useMemo для объектов/массивов
const config = useMemo(() => ({
  value: count,
  handler: () => { /* ... */ }
}), [count]);

// ❌ ПЛОХО - useMemo вместо useCallback
const handleClick = useMemo(() => {
  return () => { /* ... */ };
}, [deps]);

// ❌ ПЛОХО - useCallback для вычислений
const sorted = useCallback(() => {
  return data.sort((a, b) => a - b);
}, [data]);

Заключение

Технически useMemo может заменить useCallback:

// Эквивалентно
useCallback(fn, deps)
useMemo(() => fn, deps)

Но этого не стоит делать потому что:

  1. Намерение - useCallback явно говорит что здесь функция
  2. Производительность - useCallback чуть быстрее
  3. Читаемость - меньше кода, яснее смысл
  4. Безопасность - меньше возможностей для ошибок
  5. Конвенция - остальные разработчики ожидают useCallback

Используй правильный инструмент для правильной задачи!

Может ли useMemo заменить useCallback? | PrepBro