Что на самом деле происходит во время рендера в React

#react#frontend#rendering

Когда React-разработчики говорят «компонент перерендерился», они часто не до конца понимают, что именно это означает. На практике это может привести к путанице в рассуждениях о производительности, хуках и состоянии. Давайте разберемся, что такое рендер в React, чем он отличается от обновления DOM, и как это работает под капотом.

Рендер ≠ обновление DOM

Первое, что нужно понять: рендер — это вызов функции компонента. Это не то же самое, что обновление экрана браузером. React выполняет три шага, чтобы отобразить компонент на экране:

  1. Триггер — что-то запрашивает рендер.
  2. Рендер — React вызывает функцию компонента, чтобы вычислить, что отобразить.
  3. Коммит — React применяет результат к DOM.

Только после этого браузер рисует обновленный DOM на экране. Эти шаги важно разделять, потому что они имеют разные последствия для производительности и архитектуры приложения.

Шаг 1: Триггер

Рендер может быть вызван только двумя причинами:

  1. Начальный рендер — когда приложение монтируется впервые.
  2. Обновление состояния — изменение состояния компонента автоматически ставит рендер в очередь.

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

Шаг 2: Рендер

На этом этапе React вызывает функцию компонента. JSX, который вы возвращаете, — это описание UI, а не сам UI. Рендер рекурсивный: если ваш компонент возвращает другие компоненты, React вызывает их тоже, и так далее, пока не останется вложенных компонентов.

Важно помнить, что рендер должен быть чистой функцией: при одинаковых входных данных компонент всегда должен возвращать одинаковый JSX. Это позволяет React безопасно вызывать компонент несколько раз, например, в режиме разработки, где Strict Mode вызывает компонент дважды для поиска побочных эффектов.

Шаг 3: Коммит

Здесь React наконец взаимодействует с реальным DOM. Ключевой момент: React изменяет только те узлы DOM, которые изменились между рендерами. Ваш компонент может полностью перерендериться, но React изменит только один текстовый узел, если это необходимо.

Практический пример

Давайте посмотрим на примере, как это работает. Создадим простое React-приложение:

npm create vite@latest render-lab -- --template react
cd render-lab
npm install
npm run dev

Заменим содержимое src/App.jsx на следующий код:

import { useState } from 'react';

export default function App() {
  console.log('🔵 App function ran (a render)');

  const [count, setCount] = useState(0);

  return (
    <div style={{ fontFamily: 'sans-serif', padding: 24 }}>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <input placeholder="type here, THEN click Increment" />
    </div>
  );
}

Теперь проведем два эксперимента:

  1. Эксперимент A — лог рендера. Сколько раз вы увидите сообщение 🔵 App function ran при загрузке? В режиме разработки оно выведется дважды из-за Strict Mode. Это не баг, а способ React обнаружить побочные эффекты.

  2. Эксперимент B — рендер vs коммит. Введите текст в поле ввода, но не нажимайте Enter. Теперь несколько раз нажмите кнопку Increment. Обратите внимание, что текст в поле ввода не сбрасывается, хотя компонент полностью перерендеривается. Это потому, что React в шаге коммита увидел, что узел DOM для поля ввода не изменился, и оставил его в покое.

Вывод

Рендер в React — это вызов функции компонента, который может происходить часто, но не всегда приводит к изменению DOM. Понимание этого процесса помогает избежать лишних оптимизаций и лучше управлять производительностью приложения.

Если вы хотите глубже разобраться в теме, рекомендую прочитать документацию React: Render and Commit и Keeping Components Pure. Эти материалы помогут закрепить понимание и избежать распространенных ошибок.


Источник: https://dev.to/edwintantawi/what-a-react-render-actually-is-2d1j