React
Что такое render и когда он происходит
Новичок открывает консоль и видит, что компонент логирует себя дважды на один клик. Паника: бесконечный цикл? Утечка? На самом деле это две разные вещи, которые легко спутать. Первая - StrictMode намеренно вызывает компонент дважды при разработке, чтобы поймать нечистый код. Вторая - непонимание, что вообще такое рендер: это не рисование на экране, а всего лишь вызов функции компонента. React сначала вычисляет, как должен выглядеть UI, и только потом точечно меняет DOM. Разделение этих двух фаз снимает большинство загадок про то, почему интерфейс ведёт себя именно так.
- React DevTools умеет подсвечивать ре-рендеры компонентов - незаменимый инструмент для поиска лишних перерисовок
- Performance Tracks в React 19.2 показывают фазы рендера и коммита прямо в профайлере Chrome
- React Compiler v1.0 автоматически убирает лишние ре-рендеры, но понимать, что их вызывает, всё равно необходимо
- StrictMode включён по умолчанию в новых проектах на Next.js и Vite - двойной вызов в разработке встречают все
Предварительные знания
- useState: вызов сеттера планирует обновление компонента
- Компоненты - чистые функции от props и состояния
- Reconciliation из вводного урока: React сравнивает старое и новое описание UI
Фаза рендера против фазы коммита
Слово 'рендер' сбивает с толку: кажется, что речь про рисование на экране. На деле рендер в React - это просто вызов функции компонента. Компонент возвращает описание UI (дерево элементов), React сравнивает его с предыдущим (reconciliation) и вычисляет, что изменилось. Это фаза рендера, и она ничего не трогает в DOM. Только после неё наступает фаза коммита, когда React вносит вычисленные правки в реальный DOM.
- Триггер: появился повод обновиться - первый рендер или изменение состояния
- Рендер: React вызывает компоненты, получает новое описание UI и сравнивает со старым (reconciliation)
- Коммит: React применяет к реальному DOM только вычисленную разницу
Разделение фаз - ключ к производительности React. Вызвать функцию и сравнить объекты в памяти дёшево. Трогать реальный DOM дорого. Поэтому React сначала всё просчитывает в фазе рендера и в коммите вносит минимум изменений. Если описание UI не изменилось, фаза коммита для этого узла может вообще не понадобиться.
Именно потому, что рендер - это вызов функции, частый ре-рендер сам по себе не катастрофа: пересчёт в памяти быстр. Проблемой он становится, только когда внутри компонента идут тяжёлые вычисления. С этим борются мемоизацией, а в React 19 - автоматически через компилятор.
Что именно происходит в фазе рендера?
Что запускает ре-рендер
Поводов для рендера всего несколько, и важно знать их точно. Первый - монтирование: компонент рендерится впервые при появлении на экране. Дальше повторный рендер запускает только изменение состояния через сеттер. И есть каскадный эффект: когда компонент ре-рендерится, по умолчанию вместе с ним заново вызываются все его дочерние компоненты.
| Повод | Запускает ре-рендер |
|---|---|
| Первый рендер (монтирование) | Да, начальный |
| Вызов сеттера состояния | Да, если значение реально изменилось |
| Ре-рендер родителя | Да, дети перерисовываются по умолчанию |
| Мутация обычной переменной | Нет, React об этом не знает |
Важная деталь: изменение props само по себе не является отдельным триггером. Props меняются потому, что ре-рендерится родитель, который их передаёт. То есть корень любого ре-рендера - всегда либо монтирование, либо чей-то setState выше или внутри компонента. Обычная переменная, изменённая в обход состояния, перерисовку не вызовет вовсе.
Если сеттеру передать значение, равное текущему, React может пропустить ре-рендер - это встроенная оптимизация (bail out). Для объектов сравнение идёт по ссылке, поэтому новый объект с теми же полями всё равно считается изменением.
Что НЕ вызывает ре-рендер компонента?
Чистый рендер и StrictMode
Фаза рендера обязана быть чистой. Это значит: при одних и тех же props и состоянии компонент возвращает один и тот же JSX и не делает побочных эффектов во время рендера. Нельзя менять переменные вне компонента, мутировать props или состояние, обращаться к сети прямо в теле функции. Побочные эффекты выносят в обработчики событий или в useEffect, которые срабатывают уже после рендера.
Чтобы помочь поймать нечистый рендер, в режиме разработки работает StrictMode. Он намеренно вызывает функции компонентов дважды. Если рендер чистый, двойной вызов незаметен: результат тот же. Если же компонент мутирует что-то снаружи, как Bad выше, удвоение сразу выявит баг - значение подскочит непредсказуемо. В продакшен-сборке двойного вызова нет, накладных расходов это не создаёт.
Двойной лог в консоли при разработке - это не баг и не бесконечный цикл, а намеренное поведение StrictMode. Если же удвоение ломает логику (например, счётчик прибавляет дважды), значит, рендер нечист и его нужно исправить, а не отключать StrictMode.
Зачем StrictMode вызывает компоненты дважды в режиме разработки?
Связь с другими темами
Эта тема замыкает фундамент: становится понятно, как всё изученное складывается в работающий цикл:
- useState — Вызов сеттера - главный триггер ре-рендера, рассмотренного здесь
- Списки и keys — key используется именно в фазе reconciliation для сопоставления элементов
- React Compiler — Авто-мемоизация компилятора борется именно с лишними ре-рендерами из этого урока
Итог
- Рендер - это вызов функции компонента, который возвращает описание UI. Это не изменение экрана само по себе
- Цикл состоит из трёх фаз: триггер (повод), рендер (вызов компонентов и reconciliation), коммит (точечные правки DOM)
- Ре-рендер запускает либо первый рендер, либо изменение состояния через сеттер, либо ре-рендер родителя
- Рендер должен быть чистым: одни и те же props и состояние дают один и тот же JSX без побочных эффектов
- В режиме разработки StrictMode вызывает компоненты дважды, чтобы выявить нечистый код. В продакшене этого нет
Связанные уроки
- rc-06-usestate — Ре-рендер запускается вызовом сеттера состояния, поэтому модель useState нужна прежде понимания рендера
- rc-04-rendering-lists-keys — Сопоставление элементов по key - часть фазы reconciliation, которую разбирает этот урок
- rc-20-react-compiler — Понимание лишних ре-рендеров ведёт к React Compiler, который автоматически их убирает