Чем отличается `useMemo` от `useCallback`?
useMemo мемоизирует результат вычисления (значение), а useCallback мемоизирует саму функцию. По сути useCallback(fn, deps) — это сокращение для useMemo(() => fn, deps).Основное различие
useMemo и useCallback — оба хука мемоизации в React, но они решают разные задачи.
useMemo принимает фабричную функцию и массив зависимостей, выполняет её и возвращает закешированный результат (любое значение: объект, массив, число, строку). При изменении зависимостей фабрика перезапускается и возвращает новое значение.
useCallback принимает функцию и массив зависимостей и возвращает саму функцию, гарантируя стабильную ссылку между рендерами. При изменении зависимостей создаётся новая функция.
Формула эквивалентности
Под капотом useCallback реализован через useMemo:
// useCallback — это сокращение:
useCallback(fn, deps) === useMemo(() => fn, deps)
Когда использовать useMemo
Применяется для дорогостоящих вычислений, результат которых не должен пересчитываться на каждый рендер: фильтрация/сортировка больших массивов, сложные математические операции, формирование производных данных.
Когда использовать useCallback
Применяется для стабилизации ссылки на функцию, которая:
- передаётся как пропс в дочерний компонент, обёрнутый
React.memo - указана в массиве зависимостей
useEffect,useMemoили другогоuseCallback - используется как обработчик события в оптимизированном компоненте
Важные нюансы
Мемоизация не бесплатна. Оба хука потребляют память и добавляют накладные расходы на сравнение зависимостей. Преждевременная оптимизация с ними может замедлить код, а не ускорить его.
Референсное равенство. React сравнивает зависимости по Object.is. Примитивы сравниваются по значению, объекты и функции — по ссылке. Именно поэтому функция без useCallback создаётся заново на каждом рендере и сбивает мемоизацию дочерних компонентов.
Правило профилирования. Следует использовать React DevTools Profiler и выявлять реальные узкие места, а не оборачивать всё подряд в мемоизацию.
Сводная таблица
useMemo | useCallback | |
|---|---|---|
| Возвращает | Результат вызова функции | Саму функцию |
| Применение | Тяжёлые вычисления | Стабильные ссылки на функции |
| Типичный кейс | Фильтрация списка | Обработчик для React.memo-компонента |
Что хочет услышать интервьюер
Чёткое разграничение: useMemo кеширует значение, useCallback кеширует функцию
Понимание того, что useCallback — это частный случай useMemo с возвратом функции
Знание практических сценариев применения каждого хука (React.memo, useEffect-зависимости)
Осознание того, что мемоизация имеет собственную стоимость и не является серебряной пулей
Понимание референсного равенства и того, почему функции без useCallback пересоздаются каждый рендер
Пример: useMemo — мемоизация тяжёлого вычисления
import { useMemo, useState } from 'react';
function ProductList({ products }: { products: Product[] }) {
const [search, setSearch] = useState('');
// Пересчитывается только при изменении products или search
const filtered = useMemo(
() => products.filter(p => p.name.toLowerCase().includes(search.toLowerCase())),
[products, search]
);
return (
<>
<input value={search} onChange={e => setSearch(e.target.value)} />
{filtered.map(p => <div key={p.id}>{p.name}</div>)}
</>
);
}
Пример: useCallback — стабильная ссылка для дочернего компонента
import { useCallback, useState, memo } from 'react';
// Дочерний компонент, обёрнутый в memo — не ре-рендерится при тех же пропсах
const Button = memo(({ onClick, label }: { onClick: () => void; label: string }) => {
console.log('Button render:', label);
return <button onClick={onClick}>{label}</button>;
});
function Counter() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0);
// Без useCallback — новая ссылка на каждый рендер, memo не спасёт
const handleIncrement = useCallback(() => {
setCount(c => c + 1);
}, []); // зависимостей нет — функция создаётся один раз
return (
<>
<p>Count: {count}, Other: {other}</p>
<Button onClick={handleIncrement} label="+1" />
<button onClick={() => setOther(o => o + 1)}>Изменить other</button>
</>
);
}
Пример: Эквивалентность useCallback и useMemo
import { useMemo, useCallback } from 'react';
function Example({ id }: { id: number }) {
// Эти две записи эквивалентны:
const handleA = useCallback(() => console.log(id), [id]);
const handleB = useMemo(() => () => console.log(id), [id]);
// handleA === handleB по поведению
// useCallback просто удобнее читается для функций
}
Типичные ошибки
Путают хуки и считают, что useMemo тоже мемоизирует функцию, а не её результат
Оборачивают всё подряд в useMemo/useCallback «на всякий случай», не профилируя реальные проблемы
Забывают указать нужные зависимости или указывают лишние, нарушая корректность мемоизации
Используют useCallback для функций, которые не передаются дальше и не влияют на повторные рендеры
Не используют useCallback там, где это действительно нужно — при передаче коллбэков в React.memo-компоненты


