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

Мемоизация в React: ускоряем рендер без лишних расходов

Практические рекомендации по использованию useMemo, useCallback и React.memo для снижения количества перезапусков и экономии CPU в React-приложениях.

reactperformanceoptimizationmemoization

Если список “залипает”, а консоль полна сообщений о частых рендерах, вероятно, вы повторно вычисляете результаты, которые можно кешировать. Мемоизация в React способ сохранять результаты функций между рендерами, чтобы избежать лишней работы.

Новички часто добавляют React.memo к каждому компоненту, полагая, что это решит все проблемы. На деле такая “массовая” оптимизация может ухудшить производительность, потому что сравнение пропсов также требует ресурсов. Понимание, где действительно нужен кеш, экономит и код, и время отладки.

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


1. Что такое мемоизация и зачем она в React?

Мемоизация это кэширование результата функции при тех же входных аргументах. В React она позволяет избежать повторных вычислений во время re-render.

function expensiveCalc(items) {
  console.log("calc");
  return items.reduce((a, b) => a + b, 0);
}

// При каждом рендере будет "calc"
const total = expensiveCalc(props.numbers);
СостояниеВычисленияРендеровCPU-затраты
без кешакаждый разкаждыйвысокие
с useMemoодин разкаждыйнизкие
с React.memoпри изменении propsсредние

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


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

useMemo уместен, когда внутри компонента есть тяжелая логика, зависящая от пропсов или состояния.

function ProductList({ products, filter }) {
  const filtered = useMemo(() => {
    return products.filter((p) => p.category === filter);
  }, [products, filter]);

  return (
    <ul>
      {filtered.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}

Чек-лист для useMemo

  • Функция действительно тяжелая и ее можно вычислять реже.
  • Зависимости перечислены явно.
  • Возвращаемое значение не создается заново каждый рендер (иначе кеш не поможет).

Если зависимости часто меняются, эффект от useMemo снижается тогда ищем альтернативу.


3. Как правильно применять React.memo к компонентам?

React.memo оборачивает функциональный компонент и делает поверхностное сравнение пропсов.

const Card = React.memo(function Card({ title, onClick }) {
  console.log("render Card");
  return (
    <div onClick={onClick}>
      <h3>{title}</h3>
    </div>
  );
});
ПлюсыМинусы
Сокращает лишние рендерыСравнение поверхностное; может не заметить глубокие изменения
Простой синтаксисДобавляет небольшие накладные расходы при сборке

Важно: если onClick передается как анонимная функция, каждый рендер создает новый референс, и React.memo будет считать пропсы измененными. В этом случае стабилизировать колбэк помогает useCallback.


4. useCallback как вспомогательный инструмент

useCallback возвращает мемоизированную версию функции и работает аналогично useMemo, но для функций.

function List({ items }) {
  const handleClick = useCallback((id) => {
    console.log("clicked", id);
  }, []);

  return items.map((item) => (
    <Card
      key={item.id}
      title={item.title}
      onClick={() => handleClick(item.id)}
    />
  ));
}

Здесь handleClick стабилен, но анонимная стрелка внутри onClick все равно создает новый пропс. Можно избавиться от нее, передавая уже готовый колбэк:

const handleClick = useCallback(id => () => console.log('clicked', id), []);
...
<Card onClick={handleClick(item.id)} />

Типичные ошибки

  1. Оставлять анонимные функции в JSX приводит к лишним рендерам.
  2. Игнорировать массив зависимостей мемоизация ломается.
  3. Оборачивать каждый компонент в React.memo без профилирования может замедлить сборку.

5. Профилирование: где действительно нужна оптимизация?

Перед тем как добавить useMemo или React.memo, измерьте влияние. В Chrome DevTools → Performance:

  1. Запустите профиль без оптимизаций.
  2. Добавьте кеширование к подозрительной функции.
  3. Сравните графики “Self Time” и “Total Time”.

Если разница менее 5 % CPU, кешировать смысл нет код усложнится без выгоды.

ПоказательДействие
Self Time > 10 msДобавить useMemo/React.memo
Частые ререндеры > 3Проверить зависимости
Стабильные пропсыОставить без memo

6. Практический пример: виртуализированный список

Сценарий: список из 10 000 карточек, каждая вычисляет цену через сложный алгоритм. Решение сочетание useMemo (для расчета цены) и React.memo (для карточки).

function PriceCard({ product }) {
  const price = useMemo(() => calculatePrice(product), [product]);
  return (
    <div className="card">
      <h4>{product.name}</h4>
      <p>{price}$</p>
    </div>
  );
}

const MemoPriceCard = React.memo(PriceCard);

function ProductGrid({ products }) {
  return (
    <VirtualList // из react-window
      height={600}
      itemCount={products.length}
      itemSize={80}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          <MemoPriceCard product={products[index]} />
        </div>
      )}
    </VirtualList>
  );
}

Каждый элемент пересчитывается только при изменении своего product, а список рендерит лишь видимые элементы. При скролле нагрузка на CPU почти не меняется.


Чек-лист по оптимизации рендеров в React

  1. Выявите “дорогие” вычисления профилируйте, ищите функции с длительным временем. Используйте React Profiler для детального анализа.
  2. Применяйте useMemo только к значениям с ограниченным набором зависимостей.
  3. Оборачивайте компоненты в React.memo лишь тогда, когда пропсы действительно стабилизированы. Подробнее в статье React.memo vs useMemo.
  4. Стабилизируйте колбэки через useCallback, иначе memo будет срабатывать каждый рендер.
  5. Проверяйте эффект без измерений оптимизация может стать лишним грузом.

Мемоизация полезный, но деликатный инструмент. Используйте ее, когда действительно экономите ресурсы, а не “по привычке”.

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

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

Написать в Telegram

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