Как работает React.memo() и чем он отличается от PureComponent?

React.memo() — это функция высшего порядка (HOC), предназначенная для мемоизации функциональных компонентов. Она предотвращает повторный рендер компонента, если переданные в него props не изменились по поверхностному сравнению (shallow comparison). Это один из инструментов для оптимизации производительности в React, особенно при работе с компонентами, которые получают часто одни и те же props или вложенные структуры данных.

Как работает React.memo()

Когда компонент оборачивается в React.memo(), React сохраняет предыдущие props и сравнивает их с текущими при каждом обновлении родительского компонента. Если props не изменились (по ссылке и значению для примитивов), то компонент не перерендеривается — React просто использует результат предыдущего рендера.

Синтаксис:

const MyComponent = React.memo(function MyComponent(props) {
// компонент будет рендериться только при изменении props
return <div>{props.value}</div>;
});

Поверхностное сравнение в React.memo

React.memo сравнивает:

  • Примитивы (string, number, boolean, null, undefined) — по значению

  • Объекты, массивы и функции — по ссылке (===)

Если хотя бы один prop отличается по ссылке или значению от предыдущего, компонент будет повторно отрисован.

Пример:

const Memoized = React.memo(MyComponent);
//  приведёт к перерендеру из-за новой ссылки на объект
<Memoized options={{ sort: 'asc' }} />
//  если мемоизировать объект
const options = useMemo(() => ({ sort: 'asc' }), \[\]);
<Memoized options={options} />

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

Можно передать вторым аргументом функцию areEqual(prevProps, nextProps), чтобы вручную контролировать, считать ли props равными:

const MyComponent = React.memo((props) => {
return <div>{props.count}</div>;
}, (prevProps, nextProps) => {
return prevProps.count === nextProps.count;
});

Когда использовать React.memo

  • Компонент получает одинаковые props на протяжении времени

  • Компонент тяжёлый по рендерингу

  • Есть вложенные объекты или функции, передающиеся как props (с мемоизацией)

  • Ререндеры происходят слишком часто из-за обновлений родителя

Ограничения React.memo

  • Работает только с **функциональными компонентами
    **
  • Поверхностное сравнение может не сработать с вложенными структурами

  • Мемоизация может не окупиться, если компонент простой и рендерится быстро — в этом случае сравнение может быть дороже, чем сам рендер

  • Не влияет на внутреннее состояние компонента или контекст

Чем отличается от PureComponent

React.PureComponent — это класс, расширяющий React.Component, который реализует метод shouldComponentUpdate() с поверхностным сравнением props и state по умолчанию. Это аналог React.memo для классовых компонентов.

class MyPureComponent extends React.PureComponent {
render() {
return <div>{this.props.value}</div>;
}
}
Характеристика React.memo() PureComponent
Тип компонента Функциональный Классовый
--- --- ---
Сравнение Поверхностное (shallow) Поверхностное (shallow)
--- --- ---
Влияние на state Нет Учитывается при сравнении
--- --- ---
Способ настройки Можно задать кастомную функцию Только shouldComponentUpdate вручную
--- --- ---
Способ вызова Обёртка: React.memo(Component) Класс: extends React.PureComponent
--- --- ---
Поддержка хуков Да Нет
--- --- ---
Контроль на уровне JSX Можно использовать где угодно Только через наследование
--- --- ---

Поведение с контекстом (useContext)

Если компонент использует useContext, и значение контекста изменилось, React.memo не сможет предотвратить ререндер, даже если props не изменились. Это связано с тем, что контекст рассматривается как часть внутреннего состояния, а не props.

Решение: использовать селекторы контекста или делить контекст на более мелкие.

Пример использования с кастомным сравнением

const TodoItem = React.memo(({ todo, onToggle }) => {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
{todo.title}
&lt;/li&gt;
);
}, (prevProps, nextProps) => {
return prevProps.todo.id === nextProps.todo.id &&
prevProps.todo.completed === nextProps.todo.completed;
});

Когда React.memo может не помочь

  • Если props меняются на каждом рендере (новая ссылка на объект, массив, функция)

  • Если компонент использует useContext, и контекст часто обновляется

  • Если parent component всегда создаёт новый компонент inline (<Component prop={() => ...} />)

  • Если компонент слишком простой — тогда мемоизация неэффективна

Совместное использование с useCallback и useMemo

Чтобы React.memo работал корректно, часто нужно мемоизировать функции и объекты, передаваемые в props, иначе они будут всегда новыми по ссылке:

const onClick = useCallback(() => {
doSomething();
}, \[\]);
const config = useMemo(() => ({ type: 'basic' }), \[\]);

Поддержка в DevTools

В React DevTools компоненты, обёрнутые в React.memo, можно отличить по подписи: они отображаются как Memo(MyComponent). Это удобно для отладки и анализа.

Закулисная реализация

Под капотом React.memo создаёт обёртку с shouldComponentUpdate-подобной логикой поверх функции. Он кэширует props и результат предыдущего вызова рендера, и при совпадении возвращает кэш.

Важно понимать, что React.memo не кеширует вычисления внутри компонента — для этого нужен useMemo. Он кеширует вывод JSX при неизменившихся props.