Назад
Назад к заметкам
· 4 мин чтения

React.memo vs useMemo: когда и как использовать

Как правильно применять React.memo и useMemo для ускорения рендеров в таблицах, фильтрах и пагинации. Примеры кода и чеклист.

reactperformanceoptimizationmemoization

Введение

Если ваш список начал лагать, а консоль полна предупреждений о “лишних” перерисовках, вы уже знаете, что нужно искать оптимизацию. Часто в такой момент первым на ум приходит React.memo или useMemo. Они оба обещают “меньше работы”, но работают по-разному. Ошибки в их применении приводят к тому, что код становится громоздким, а выигрыша нет.

Разобрать, что где лучше ставить, полезно не только для больших таблиц. Это касается любой интерактивной UI-страницы: формы, дашборды, модальные окна. Понимание различий помогает писать чистый код и избежать “мемо-переусложнения”, когда мемоизация только усложняет поддержку.

Рассмотрим реальную задачу компонент-таблицу с фильтрами и сортировкой и шаг за шагом разберем, где уместен React.memo, а где useMemo. К концу вы сможете выбирать инструмент, а не бросать оба сразу.

1. Что мемоизирует React.memo?

React.memo это HOC (Higher-Order Component), который сравнивает текущие props с предыдущими. Если они “равны” (shallow compare), React пропустит перерисовку дочернего компонента.

import React from "react";

const Row = React.memo(({ item }) => {
  console.log("Row render", item.id);
  return <div>{item.title}</div>;
});
ПлюсМинус
Сокращает количество рендеров при неизменных пропсахНе помогает, если пропсы часто меняются по-ссылке
Легко добавить к любой функции-компонентуТребует тщательной проверки props (deep сравнение дорого)

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

  • Компонент получает простые, неизменные пропсы (примитивы, объекты, которые не пере-создаются каждый рендер).
  • Перерисовка дорогая (сложные расчеты, SVG, анимации).

2. Что кеширует useMemo?

useMemo хук, который запоминает результат функции до тех пор, пока не изменятся зависимости. Он не предотвращает рендер, а лишь экономит вычисления внутри рендера.

const filtered = React.useMemo(() => {
  return items.filter((item) => item.category === filter);
}, [items, filter]);
ПлюсМинус
Кеширует тяжелые вычисления (фильтрация, сортировка)Не спасет от лишних рендеров, если компонент все равно будет перерисован
Позволяет контролировать зависимости явноМожет стать “запаской”, если вычисления легкие лишний код

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

  • Вычисление требует значительных ресурсов (поиск, трансформы, большие массивы).
  • Вычисление зависит от нескольких значений, меняющихся реже, чем каждый рендер.

3. Сценарий: таблица с фильтрами и пагинацией

Представим компонент DataTable. Он получает массив rows, текущий filter и page. Без мемоизации каждый ввод в поле фильтра приводит к полной пере-вычислению и перерисовке всех строк.

function DataTable({ rows, filter, page }) {
  const filteredRows = React.useMemo(() => {
    return rows.filter((r) => r.name.includes(filter));
  }, [rows, filter]);

  const pageRows = React.useMemo(() => {
    const start = page * 10;
    return filteredRows.slice(start, start + 10);
  }, [filteredRows, page]);

  return (
    <div>
      {pageRows.map((row) => (
        <Row key={row.id} item={row} />
      ))}
    </div>
  );
}

Здесь useMemo отвечает за тяжелые операции (фильтрация, пагинация). Но строки-компоненты все равно могут перерисовываться каждый раз, если их props меняются.

4. Комбинируем React.memo и useMemo

Самый эффективный паттерн использовать оба инструмента: useMemo для расчетов, React.memo для “дешевых” строк.

const Row = React.memo(({ item }) => {
  console.log("render row", item.id);
  return <div>{item.title}</div>;
});

function DataTable({ rows, filter, page }) {
  const filteredRows = React.useMemo(
    () => rows.filter((r) => r.name.includes(filter)),
    [rows, filter]
  );
  const pageRows = React.useMemo(() => {
    const start = page * 10;
    return filteredRows.slice(start, start + 10);
  }, [filteredRows, page]);

  return (
    <div>
      {pageRows.map((row) => (
        <Row key={row.id} item={row} />
      ))}
    </div>
  );
}

Таблица теперь делает минимум работы: фильтрация один раз, а отдельные строки только если действительно изменились их данные.

5. Частые ошибки и антипаттерны

  1. Мемоизировать все подряд. React.memo без необходимости приводит к “прокисанию” кода, а useMemo для простых вычислений создает нагрузку на сравнение зависимостей.
  2. Неправильные зависимости. Пропуск нужной зависимости в массиве useMemo приводит к устаревшим результатам.
  3. Сравнение по ссылке. Если вы передаете массив/объект, который каждый рендер создается заново, React.memo будет считать их разными и рендер будет происходить.
  4. Забыть про useCallback. Иногда вместо мемоизации функции лучше использовать useCallback, чтобы стабилизировать ссылки, передаваемые в дочерние компоненты.
  5. Перепутать цель. React.memo про рендер, useMemo про вычисления. Понимание этого различия спасает от большинства путаниц.

6. Чеклист перед продакшеном

  • Выделил тяжелые расчеты в useMemo? Проверил, что зависимости покрывают все входные данные.
  • Обернул часто-перерисовываемые дочерние компоненты в React.memo? Убедился, что их props стабильно сравниваются (примитивы, memo-объекты).
  • Протестировал профайлером React DevTools: есть ли реальное снижение времени рендера? Подробнее о профилировании смотрите React Profiler: практический гайд.
  • Не забываю про useCallback для функций-обработчиков, передаваемых в мемоизированные компоненты.
  • Удалил лишнюю мемоизацию, если измерения показывают, что она не дает выгоды.

Когда ваш интерфейс начинает “тормозить” от постоянных перерисовок, а данные тяжело фильтровать каждый кадр, комбинирование React.memo и useMemo становится простым и надежным решением.

Если списки становятся очень большими, рассмотрите виртуализацию списков.

Это именно тот набор инструментов, который позволяет держать рендер быстрым без излишней сложности кода.

Готов начать?

Выбери удобный способ связи — напиши напрямую или оставь заявку

Написать в Telegram

Отвечу в течение пары часов