Что такое React.memo и зачем он нужен?
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 работает с классовыми компонентами


