Что такое React.memo и зачем он нужен?

MiddleReact · Frontend·Обновлено 1 июля 2026
Коротко
React.memo — это HOC (компонент высшего порядка), который мемоизирует результат рендера функционального компонента и пропускает повторный рендер, если входные пропсы не изменились. Используется для оптимизации производительности при наличии «дорогих» в рендере дочерних компонентов.

React.memo

React.memo — это функция высшего порядка (Higher-Order Component), встроенная в React. Она оборачивает функциональный компонент и кэширует его последний отрендеренный результат. При следующем рендере родителя React сравнивает старые и новые пропсы; если они совпадают (поверхностное сравнение), компонент не перерисовывается — возвращается сохранённый результат.

Проблема без React.memo

По умолчанию React перерендеривает дочерние компоненты каждый раз, когда перерендеривается их родитель — даже если пропсы не изменились. Для лёгких компонентов это некритично, но тяжёлые компоненты (большие списки, сложные вычисления в теле компонента) могут значительно замедлить UI.

Синтаксис

const MemoizedComponent = React.memo(MyComponent);

// С кастомной функцией сравнения
const MemoizedComponent = React.memo(MyComponent, (prevProps, nextProps) => {
  // Возвращаем true, если компонент НЕ нужно перерисовывать
  return prevProps.id === nextProps.id;
});

Когда применять

  • Компонент рендерится часто, но его пропсы меняются редко.
  • Компонент визуально «дорогой»: большой список, сложная вёрстка, тяжёлые вычисления.
  • Родительский компонент обновляется по причинам, не связанным с данным дочерним компонентом (например, обновляется локальный стейт родителя).

Важные нюансы

Поверхностное сравнение. По умолчанию React.memo сравнивает пропсы через Object.is. Объекты и массивы, создаваемые заново при каждом рендере родителя, всегда будут «новыми» — мемоизация не сработает. Здесь помогают useMemo и useCallback.

Только функциональные компоненты. React.memo не работает с классовыми компонентами — для них используется PureComponent или shouldComponentUpdate.

Не злоупотреблять. Само по себе сравнение пропсов тоже стоит ресурсов. React.memo даёт выигрыш только тогда, когда стоимость сравнения меньше стоимости пропущенного рендера.

React.memo + useCallback

Часто используются в паре: если родитель передаёт коллбэк, он должен быть стабильным (через useCallback), иначе мемоизация бесполезна.

// Родительский компонент
const Parent = () => {
  const [count, setCount] = useState(0);

  // Без useCallback функция создаётся заново → дочерний компонент перерисовывается
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Увеличить: {count}</button>
      <ExpensiveChild onAction={handleClick} />
    </>
  );
};

// Дочерний компонент защищён мемоизацией
const ExpensiveChild = React.memo(({ onAction }: { onAction: () => void }) => {
  console.log('ExpensiveChild рендерится');
  return <button onClick={onAction}>Действие</button>;
});

Итог

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

Что хочет услышать интервьюер

Кандидат объяснит, что React.memo — это HOC для мемоизации результата рендера функционального компонента

Упомянет, что сравнение пропсов поверхностное (shallow comparison) и объекты/функции нужно стабилизировать через useMemo/useCallback

Расскажет, когда применять: часто рендерящийся дорогой компонент с редко меняющимися пропсами

Понимает, что React.memo не панацея — само сравнение имеет стоимость, и злоупотреблять им не стоит

Знает аналог для классовых компонентов — PureComponent

Пример: Базовое использование React.memo

import React, { useState, useCallback } from 'react';

interface ProductCardProps {
  id: number;
  name: string;
  price: number;
  onAddToCart: (id: number) => void;
}

// Компонент не перерисовывается, если пропсы не изменились
const ProductCard = React.memo(({ id, name, price, onAddToCart }: ProductCardProps) => {
  console.log(`ProductCard ${id} рендерится`);
  return (
    <div>
      <h3>{name}</h3>
      <p>{price} ₽</p>
      <button onClick={() => onAddToCart(id)}>В корзину</button>
    </div>
  );
});

const ProductList = () => {
  const [cartCount, setCartCount] = useState(0);

  // useCallback обязателен: без него каждый рендер создаёт новую функцию
  // и React.memo не спасёт от повторных рендеров дочернего компонента
  const handleAddToCart = useCallback((id: number) => {
    setCartCount(c => c + 1);
    console.log(`Товар ${id} добавлен в корзину`);
  }, []);

  return (
    <div>
      <p>Товаров в корзине: {cartCount}</p>
      <ProductCard id={1} name="Курс по React" price={4990} onAddToCart={handleAddToCart} />
      <ProductCard id={2} name="Курс по TypeScript" price={3990} onAddToCart={handleAddToCart} />
    </div>
  );
};

Пример: Кастомная функция сравнения пропсов

interface UserAvatarProps {
  userId: number;
  avatarUrl: string;
  // Допустим, метаданные не влияют на отображение
  meta: Record<string, unknown>;
}

const UserAvatar = React.memo(
  ({ userId, avatarUrl }: UserAvatarProps) => {
    console.log(`UserAvatar ${userId} рендерится`);
    return <img src={avatarUrl} alt={`Аватар пользователя ${userId}`} />;
  },
  // Игнорируем meta — сравниваем только то, что реально влияет на рендер
  (prevProps, nextProps) =>
    prevProps.userId === nextProps.userId &&
    prevProps.avatarUrl === nextProps.avatarUrl
);

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

Оборачивают React.memo все компоненты подряд «на всякий случай», не профилируя реальную производительность

Передают новые объекты/массивы/функции в пропсы при каждом рендере и не понимают, почему мемоизация не работает

Путают React.memo (мемоизация компонента) с useMemo (мемоизация значения) и useCallback (мемоизация функции)

Не знают о возможности передать собственную функцию сравнения вторым аргументом в React.memo

Считают, что React.memo работает с классовыми компонентами

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

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

TypeScript с нуля

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

Feature-Sliced Design

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

Next.js - с нуля

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