Найти в Дзене

Оптимизация рендеринга в React: от ререндеров до Concurrent Mode

В 2026 году React стал мощнее, умнее и сложнее. Появились Server Components, Actions, Streaming SSR — но рендеринг на клиенте всё ещё остаётся узким местом многих приложений. Особенно когда компоненты начинают мигать, лагать или «подвисать» при взаимодействии. Проблема не в React. Проблема — в том, как мы его используем. React обновляет UI путём сравнения нового и старого Virtual DOM. Если компонент получил новые props, state или его родитель обновился — он перерисовывается. Но! Ререндер != перерисовка в DOM.
React может вызвать функцию компонента сотню раз — и ни разу не тронуть реальный DOM, если результат идентичен. Когда это вызывает проблемы?
— Когда Child тяжёлый (рендерит таблицу из 1000 строк).
— Когда в Child создаются новые объекты/функции следовательно ломается мемоизация.
— Когда ререндеры вызывают каскадные обновления по всему дереву. Откройте React DevTools → Profiler. Запишите взаимодействие (например, фильтрация списка). Посмотрите: Сколько компонентов обновилось?
Ск
Оглавление

В 2026 году React стал мощнее, умнее и сложнее. Появились Server Components, Actions, Streaming SSR — но рендеринг на клиенте всё ещё остаётся узким местом многих приложений. Особенно когда компоненты начинают мигать, лагать или «подвисать» при взаимодействии.

Проблема не в React. Проблема — в том, как мы его используем.

Почему компоненты ререндерятся? (И почему это не всегда плохо)

React обновляет UI путём сравнения нового и старого Virtual DOM. Если компонент получил новые props, state или его родитель обновился — он перерисовывается.

Но! Ререндер != перерисовка в DOM.
React может вызвать функцию компонента сотню раз — и ни разу не тронуть реальный DOM, если результат идентичен.

-2

Когда это вызывает проблемы?
— Когда Child тяжёлый (рендерит таблицу из 1000 строк).
— Когда в Child создаются новые объекты/функции следовательно ломается мемоизация.
— Когда ререндеры вызывают каскадные обновления по всему дереву.

1. Профилируйте

Откройте React DevTools → Profiler.

Запишите взаимодействие (например, фильтрация списка).

Посмотрите: Сколько компонентов обновилось?
Сколько времени занял рендер?
Есть ли «лишние» ререндеры?

Если компонент рендерится за 0.2 мс — не трогайте его. Оптимизация здесь — преждевременная.

Убираем ненужные ререндеры

Используем React.memo — но осознанно

React.memo предотвращает ререндер, если props не изменились по ссылке.

-3

Но! Если вы передаёте новую функцию или объект при каждом рендере — мемоизация бесполезна:

-4

React.memo имеет смысл только если:

  • Компонент рендерит много элементов,
  • Он используется в списке,
  • Его props стабильны.

Разделите «тяжёлые» операции

Если у вас есть компонент, который:

  • Парсит большой JSON,
  • Сортирует массив из 10k элементов,
  • Строит сложную визуализацию,

— вынесите эту логику в хук и оберните в useMemo:

-5

Без useMemo фильтрация будет запускаться при каждом рендере, даже если filters не менялись.

Используйте Concurrent Mode

Concurrent Mode — не «фича», а новая парадигма работы с интерактивностью. Она позволяет React:

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

Как это применить на практике?

  1. Оберните медленные компоненты в <Suspense>:
-6

2. Используйте startTransition для несрочных обновлений:

-7

Пользователь продолжает печатать без лагов, даже если поиск занимает 200 мс.

3. Разбивайте большие списки через useDeferredValue:

-8

Архитектурные паттерны

1. Colocation состояния

Держите состояние как можно ближе к тому, кто его использует. Не поднимайте всё в корень!

2. Компоненты-контейнеры

-9

Теперь UserList не ререндерится, если useUsers() не обновился.

3. Windowing для больших списков

Используйте react-window или virtuoso — они рендерят только видимые элементы.

-10

оптимизируйте то, что тормозит

React предоставляет мощные инструменты:

  • React.memo, useCallback, useMemo — для контроля ререндеров,
  • Concurrent Rendering, startTransition, useDeferredValue — для плавного UX,
  • Suspense, streaming — для прогрессивной загрузки.

Но главный принцип остаётся неизменным:

Не оптимизируйте, пока не измерили. Не усложняйте, пока не доказали выгоду.

Начните с профайлера. Уберите 2–3 лишних ререндера. Оберните тяжёлый компонент в Suspense. И вы получите ощутимый прирост — без переписывания всего приложения.

Потому что лучшая оптимизация — это та, которая решает реальную проблему пользователя. А не ту, что кажется «правильной» в теории.