Перейти к содержанию
Innovialabs

Оптимизация производительности React: memo, useMemo и ререндеры

Продвинутое3 мин

Коротко

Сначала измерьте в React Profiler, что именно тормозит; затем уберите причину лишних ререндеров структурой компонентов и только потом точечно добавляйте memo, useMemo и виртуализацию.

Короткий ответ

Ререндер сам по себе — не проблема: React рассчитан на частые перерисовки. Проблема — когда дорогие компоненты перерисовываются без нужды и это заметно пользователю. Порядок действий всегда один: измерить → найти причину → исправить структуру → мемоизировать точечно.

Почему компонент перерисовывается

Компонент рендерится заново, если:

  • изменилось его состояние;
  • перерисовался родитель (даже если пропсы те же);
  • изменилось значение контекста, который он читает.

Второй пункт — главный источник лишних ререндеров. Большой компонент со состоянием наверху дерева тянет за собой всё, что ниже.

Как найти лишние ререндеры

  1. Установите React Developer Tools и откройте вкладку Profiler.
  2. Включите в настройках запись причин рендера («why did this render»).
  3. Запишите проблемное действие: ввод в поле, открытие списка, переключение фильтра.
  4. Смотрите на flamegraph: какие компоненты рендерились, сколько времени заняли и почему.

Проверяйте на production-сборке и при необходимости с замедлением CPU в DevTools браузера: в режиме разработки React работает медленнее и картина искажается.

Сначала структура, потом мемоизация

Многие проблемы решаются без memo:

  • Опустите состояние вниз. Если поле поиска меняет состояние, держите его в маленьком компоненте SearchInput, а не в странице целиком.
  • Передавайте тяжёлые части через children. Элементы, созданные снаружи и переданные как children, не перерисовываются при изменении состояния обёртки.
  • Разделяйте контексты. Часто меняющиеся данные не должны лежать в одном контексте с редко меняющимися.
  • Не создавайте компоненты внутри компонентов. Такой компонент пересоздаётся на каждом рендере и теряет состояние.

memo, useMemo и useCallback

ИнструментЧто делаетКогда помогает
React.memoПропускает рендер, если пропсы не изменилисьТяжёлый компонент часто получает те же пропсы
useMemoКеширует результат вычисленияДорогое вычисление или объект для мемо-компонента
useCallbackКеширует функциюФункция передаётся в мемо-компонент или в зависимости эффекта
const Row = memo(function Row({ item, onSelect }) {
  return <li onClick={() => onSelect(item.id)}>{item.name}</li>;
});

function List({ items }) {
  const [selected, setSelected] = useState(null);
  const handleSelect = useCallback((id) => setSelected(id), []);
  return items.map((item) => (
    <Row key={item.id} item={item} onSelect={handleSelect} />
  ));
}

Без useCallback здесь handleSelect создавалась бы заново на каждом рендере и memo у Row не работал бы.

Когда мемоизация вредит

  • Сравнение пропсов тоже стоит времени. Для лёгких компонентов оно может быть дороже самого рендера.
  • Код усложняется, растёт риск ошибок в массивах зависимостей.
  • Мемоизация ломается незаметно: достаточно передать новый объект style={{...}} или стрелочную функцию — и memo перестаёт срабатывать.

Добавляйте мемоизацию только там, где Profiler показал реальную проблему.

Виртуализация длинных списков

Если на экране тысячи строк, проблема не в ререндерах, а в количестве DOM-узлов. Виртуализация рендерит только видимые элементы и небольшой запас вокруг. Для этого есть библиотеки, например TanStack Virtual или react-window. Альтернатива — пагинация или подгрузка по мере прокрутки.

React Compiler

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

Другие частые причины тормозов

  • Тяжёлые бандлы — используйте разделение кода и lazy.
  • Синхронные вычисления на каждый ввод — помогают useDeferredValue и useTransition.
  • Большие несжатые изображения и анимации, вызывающие перерасчёт раскладки.

Частые вопросы

Нет. Это добавляет сложность и стоимость сравнения пропсов. Мемоизируйте компоненты, которые по данным Profiler дорого рендерятся и часто получают те же пропсы.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.