Реальные издержки ререндеров в React: когда оптимизация — это про архитектуру

#react#performance#rendering

Я долго считал, что проблемы с производительностью в React — это всегда про медленные API, тяжелые алгоритмы или кривые запросы к базе. Ререндеры? Да кому они мешают, Virtual DOM же! Но когда в одном из проектов экран начал тормозить на 800ms при клике на кнопку, пришлось разбираться.

🔍 Проблема: латентные ререндеры

Первое, что сбивает с толку — приложение не падает, а просто “подтупливает”. Нет явных ошибок, но интерфейс перестает быть отзывчивым. В моем случае:

Профилирование через React DevTools показало: проблема не в одном тяжелом компоненте, а в сотнях мелких ререндеров по всему приложению. Компоненты, которые вообще не зависели от изменившихся данных, все равно выполняли свою render-логику.

📊 Почему это дорого

Даже если DOM не обновляется, каждый ререндер требует:

  1. Вызова функционального компонента
  2. Пересчета всех хуков внутри
  3. Генерации нового JSX
  4. Сравнения в Virtual DOM

На примере нашего проекта:

// Было: состояние в корне
const App = () => {
  const [user, setUser] = useState(null); // Обновляется редко
  // ...100+ дочерних компонентов
}

// Стало: поднятие контента + опускание состояния
const UserProfile = () => {
  const [user, setUser] = useState(null);
  return <ProfileContent user={user} />;
}

После рефакторинга количество ненужных ререндеров упало на 70% только за счет правильного размещения состояния.

🛠️ Три паттерна для исправления

  1. Локализация состояния
    Перенос useState ближе к месту использования сокращает “зону поражения” при обновлении. Если компонент <Header> не использует userData, зачем ему ререндериться при его изменении?

  2. Стабильные ссылки
    Функции и объекты в пропсах — главные враги:

    // Плохо: новая функция на каждый рендер
    <Button onClick={() => submit(data)} />
    
    // Лучше: useCallback или вынос за пределы компонента
    const handleSubmit = useCallback(() => submit(data), [data]);
    <Button onClick={handleSubmit} />
    
  3. Композиция вместо монолитов
    Разбиваем “боговые” компоненты на:

    • Контейнеры (логика + состояние)
    • Presentational (чистый UI)
    • Утилиты (хуки, HOC)

⚡ Что дало результат

После рефакторинга:

Главный инсайт: проблемы с производительностью в React редко решаются точечными оптимизациями. Чаще всего это следствие неправильной компоновки компонентов и состояния. Прежде чем хвататься за useMemo, стоит проверить — а тот ли компонент вообще должен ререндериться?

Для глубокого анализа рекомендую:

  1. React DevTools Profiler
  2. why-did-you-render
  3. Ручной замер через performance.now()

Как показывает практика, 80% проблем решается не memo-хуками, а нормальной компонентной архитектурой. Остальные 20% — это уже edge cases, где действительно нужны тонкие оптимизации.


Источник: https://dev.to/akshay_sarak/the-real-cost-of-re-renders-in-react-2j77