useMemo vs useCallback: подробное руководство по мемоизации в React

16 марта 2026
Автор

Олег Марков

useMemo vs useCallback в React

useMemo и useCallback — хуки оптимизации в React, которые предотвращают лишние вычисления и рендеры через мемоизацию. Они похожи, но служат разным целям.

Основная разница

useMemo    → мемоизирует РЕЗУЛЬТАТ вычисления (значение)
useCallback → мемоизирует ФУНКЦИЮ (ссылку на функцию)

По сути, useCallback(fn, deps) — это сокращение для useMemo(() => fn, deps).

useMemo

useMemo кэширует результат вызова функции и пересчитывает его только при изменении зависимостей.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Когда использовать useMemo

1. Дорогостоящие вычисления

function DataAnalytics({ transactions }) {
  // ✅ Пересчитывается только при изменении transactions
  const statistics = useMemo(() => {
    console.log('Calculating statistics...');

    return {
      total: transactions.reduce((sum, t) => sum + t.amount, 0),
      average: transactions.reduce((sum, t) => sum + t.amount, 0) / transactions.length,
      max: Math.max(...transactions.map(t => t.amount)),
      min: Math.min(...transactions.map(t => t.amount)),
      byCategory: transactions.reduce((acc, t) => {
        acc[t.category] = (acc[t.category] || 0) + t.amount;
        return acc;
      }, {} as Record<string, number>),
    };
  }, [transactions]);

  return (
    <div>
      <p>Итого: {statistics.total}</p>
      <p>Среднее: {statistics.average.toFixed(2)}</p>
    </div>
  );
}

2. Стабильная ссылка на объект для дочернего компонента

function ParentComponent({ userId, theme }) {
  // ❌ Без useMemo: новый объект при каждом рендере Parent
  const config = { userId, theme, timestamp: Date.now() };

  // ✅ С useMemo: тот же объект, если userId и theme не изменились
  const config = useMemo(
    () => ({ userId, theme, timestamp: Date.now() }),
    [userId, theme]
  );

  return <ExpensiveChild config={config} />;
}

const ExpensiveChild = React.memo(({ config }) => {
  // Перерендерится только если config изменился по ссылке
  console.log('ExpensiveChild render');
  return <div>{config.userId}</div>;
});

3. Фильтрация и сортировка списков

function ProductCatalog({ products, searchQuery, sortBy, filterCategory }) {
  const processedProducts = useMemo(() => {
    let result = [...products];

    // Фильтрация
    if (filterCategory !== 'all') {
      result = result.filter(p => p.category === filterCategory);
    }

    // Поиск
    if (searchQuery) {
      const query = searchQuery.toLowerCase();
      result = result.filter(p =>
        p.name.toLowerCase().includes(query) ||
        p.description.toLowerCase().includes(query)
      );
    }

    // Сортировка
    result.sort((a, b) => {
      if (sortBy === 'price') return a.price - b.price;
      if (sortBy === 'name') return a.name.localeCompare(b.name);
      return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
    });

    return result;
  }, [products, searchQuery, sortBy, filterCategory]);

  return <ProductList products={processedProducts} />;
}

useCallback

useCallback кэширует ссылку на функцию и создаёт новую функцию только при изменении зависимостей.

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

Когда использовать useCallback

1. Передача функций в мемоизированные дочерние компоненты

function TodoList({ todos }) {
  const [filter, setFilter] = useState('all');

  // ❌ Без useCallback: новая функция при каждом рендере TodoList
  const handleToggle = (id) => {
    setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t));
  };

  // ✅ С useCallback: та же функция, если зависимости не изменились
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t));
  }, []); // Нет зависимостей — функциональное обновление setState

  return todos.map(todo => (
    <TodoItem
      key={todo.id}
      todo={todo}
      onToggle={handleToggle}
    />
  ));
}

const TodoItem = React.memo(({ todo, onToggle }) => {
  console.log('TodoItem render:', todo.id);
  return (
    <li onClick={() => onToggle(todo.id)}>
      {todo.text}
    </li>
  );
});

2. Зависимость в useEffect

function SearchComponent({ query }) {
  const [results, setResults] = useState([]);

  // ✅ useCallback: стабильная ссылка для useEffect
  const performSearch = useCallback(async (searchQuery) => {
    const data = await api.search(searchQuery);
    setResults(data);
  }, []); // Функция не зависит от внешних переменных

  useEffect(() => {
    performSearch(query);
  }, [query, performSearch]); // performSearch стабильна — нет лишних вызовов

  return <ResultsList results={results} />;
}

3. Event handlers с зависимостями

function Form({ onSubmit, userId }) {
  const [data, setData] = useState({ name: '', email: '' });

  // ✅ useCallback: пересоздаётся только при изменении onSubmit или userId
  const handleSubmit = useCallback(
    (e: React.FormEvent) => {
      e.preventDefault();
      onSubmit({ ...data, userId });
    },
    [data, onSubmit, userId]
  );

  return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}

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

useMemo useCallback
Мемоизирует Результат вычисления Ссылку на функцию
Возвращает Любое значение Функцию
Синтаксис useMemo(() => value, deps) useCallback(fn, deps)
Типичное применение Дорогие вычисления, объекты Обработчики событий, колбэки
Эквивалент useMemo(() => fn, deps)

Когда НЕ использовать мемоизацию

Мемоизация имеет стоимость: память для хранения кэша и сравнение зависимостей. Не стоит её применять везде.

// ❌ Избыточная мемоизация — примитивное значение
const doubledCount = useMemo(() => count * 2, [count]);
// Проще написать: const doubledCount = count * 2;

// ❌ Избыточная мемоизация — дешёвая операция
const greeting = useMemo(() => `Hello, ${name}!`, [name]);
// Проще написать: const greeting = `Hello, ${name}!`;

// ❌ Избыточная мемоизация — компонент всё равно рендерится
function Component() {
  // Если Parent рендерится, Child тоже рендерится
  // useCallback не поможет без React.memo на дочернем компоненте
  const handleClick = useCallback(() => console.log('click'), []);
  return <SimpleChild onClick={handleClick} />; // Без React.memo — бесполезно
}

// ✅ Когда useCallback полезен — с React.memo
const MemoizedChild = React.memo(SimpleChild);
function Component() {
  const handleClick = useCallback(() => console.log('click'), []);
  return <MemoizedChild onClick={handleClick} />; // Теперь работает!
}

Практический пример: поисковый компонент

interface SearchProps {
  items: Product[];
  onSelect: (product: Product) => void;
}

function ProductSearch({ items, onSelect }: SearchProps) {
  const [query, setQuery] = useState('');
  const [category, setCategory] = useState('all');

  // useMemo: фильтрация и поиск — потенциально дорогая операция
  const filteredItems = useMemo(() => {
    return items.filter(item => {
      const matchesQuery = query
        ? item.name.toLowerCase().includes(query.toLowerCase())
        : true;
      const matchesCategory =
        category === 'all' || item.category === category;
      return matchesQuery && matchesCategory;
    });
  }, [items, query, category]);

  // useCallback: стабильный обработчик для мемоизированного дочернего компонента
  const handleSelect = useCallback(
    (product: Product) => {
      onSelect(product);
    },
    [onSelect]
  );

  // useCallback: обработчик изменения запроса
  const handleQueryChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setQuery(e.target.value);
    },
    []
  );

  return (
    <div>
      <input
        value={query}
        onChange={handleQueryChange}
        placeholder="Поиск..."
      />
      <CategoryFilter value={category} onChange={setCategory} />
      <ProductGrid items={filteredItems} onSelect={handleSelect} />
    </div>
  );
}

React Compiler и будущее мемоизации

React 19 представил React Compiler (ранее React Forget), который автоматически добавляет мемоизацию там, где это нужно. С его использованием ручное применение useMemo и useCallback становится менее необходимым.

// С React Compiler: можно писать простой код
// Компилятор сам добавит useMemo/useCallback где нужно
function Component({ data, onUpdate }) {
  const processed = processData(data); // Компилятор мемоизирует если нужно
  const handleClick = () => onUpdate(processed); // Компилятор мемоизирует если нужно
  return <Child data={processed} onClick={handleClick} />;
}

Правило выбора

Мемоизирую вычисление (число, объект, массив)?
  └── useMemo

Мемоизирую функцию (обработчик событий, колбэк)?
  └── useCallback

Простое значение или операция — нужна ли мемоизация?
  └── Скорее всего НЕТ. Начни без неё и профилируй.

Профилирование с React DevTools

Перед оптимизацией убедись, что проблема действительно есть:

// Оберни в Profiler для измерения
import { Profiler } from 'react';

function App() {
  const onRender = (id, phase, actualDuration) => {
    console.log(`${id} (${phase}): ${actualDuration}ms`);
  };

  return (
    <Profiler id="ProductCatalog" onRender={onRender}>
      <ProductCatalog {...props} />
    </Profiler>
  );
}

Резюме

  • useMemo — для мемоизации значений: дорогих вычислений, объектов с стабильной ссылкой
  • useCallback — для мемоизации функций: передаваемых в React.memo-компоненты или используемых в зависимостях useEffect
  • Не мемоизируй всё подряд — сначала профилируй, потом оптимизируй
  • React Compiler в React 19 автоматизирует большую часть этой работы

Дополнительные ресурсы

Стрелочка влевоКак предотвратить лишние ре-рендеры в React: полное руководствоПравила хуков — правила использованияСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Uncontrolled Components: когда DOM управляет даннымиRender Props: гибкое управление рендерингом в ReactПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаЛенивая загрузка: как ускорить React-приложение в разыHOC в React: мастерство композиции компонентовuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIКомпозиция компонентов в React: строим гибкие интерфейсыCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с данными
useState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияuseEffect в React что это и как использоватьuseContext — работа с контекстом в ReactОптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextКак предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
StrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать элементы в ReactКак использовать React DOM в проектеЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOM
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий