Как работает 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}
</li>
);
}, (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.