Лишние ре-рендеры и как их найти (Profiler)

Урок про то, как найти лишние ре-рендеры с помощью React DevTools Profiler — прежде чем что-либо оптимизировать.

Лишний ре-рендер — это рендер компонента, который не привёл ни к каким изменениям видимого результата. Сам по себе он не «ошибка», но если повторяется часто и в тяжёлом компоненте — это цель для оптимизации.

Сначала измерь, потом чини

Главная ошибка новичка в производительности — раскидать React.memo и useMemo «на всякий случай». Это добавляет сложность и сравнения, которые сами стоят времени. Правильный порядок: замерить → найти узкое место → оптимизировать именно его → перезамерить. Инструмент для замера — React DevTools Profiler.

React DevTools Profiler — обзор

Установите расширение React Developer Tools для браузера. Во вкладке Profiler:

  1. Нажмите «запись», выполните действие в приложении (клик, ввод текста), остановите запись.
  2. Profiler покажет flame graph (пламенный граф) каждого коммита: какие компоненты рендерились и сколько это заняло.
  3. Серые компоненты не рендерились в этом коммите; цветные — рендерились, цвет и ширина отражают время.
  4. Включите «Record why each component rendered» — DevTools подпишет причину: изменился проп, состояние, контекст или родитель.

Что искать

Симптом в ProfilerВероятная причина
Большое поддерево рендерится на каждый ввод символаСостояние формы поднято слишком высоко; стоит локализовать
Тяжёлый список перерисовывается, хотя данные те жеРодитель передаёт новый объект/функцию пропсом каждый рендер
«Why rendered: parent» у дорогого листаКандидат на React.memo
Один и тот же компонент в коммите 50 разПроблема с ключами в списке

Дешёвая «ручная» диагностика

Без Profiler можно временно вставить console.log прямо в тело компонента — он печатается на каждый рендер. Это грубо, но мгновенно отвечает на вопрос «сколько раз рендерится этот компонент при этом действии».

function ExpensiveList({ items }) {
  console.log("ExpensiveList рендер, items.length =", items.length);
  return (
    <ul>
      {items.map((it) => (
        <li key={it.id}>{it.name}</li>
      ))}
    </ul>
  );
}

Если этот лог сыплется при действиях, которые items не меняют, — у вас лишние ре-рендеры, и теперь понятно, куда смотреть.

Когда лишний ре-рендер можно игнорировать

Если компонент лёгкий (несколько узлов DOM, без тяжёлых вычислений), пара лишних рендеров незаметна — оптимизация только навредит читаемости. Реагируйте на ре-рендеры, которые видны в Profiler как заметное время или ощущаются как лаги интерфейса.

Итог

  • Profiler показывает, какие компоненты рендерились, сколько это заняло и почему.
  • Включайте «why did this render», чтобы видеть причину: проп, состояние, контекст, родитель.
  • Оптимизируйте только то, что реально дорого и часто. Остальное — оставьте простым.
Проверьте себя
1. Каков правильный порядок работы с производительностью?
AОбернуть всё в memo заранее
BЗамерить, найти узкое место, оптимизировать его, перезамерить
CУдалить useState везде
DСразу переписать на классы
2. Что показывает опция «Record why each component rendered» в Profiler?
AРазмер бандла
BПричину рендера: проп, состояние, контекст или родитель
CКоличество строк кода
DИспользование памяти браузера
3. Когда лишним ре-рендером можно пренебречь?
AНикогда, любой рендер — баг
BЕсли компонент лёгкий и рендер незаметен в Profiler
CТолько в продакшене
DЕсли используется TypeScript
Поддержать проект