Урок по треку React 7 мин

Как React рендерит и сравнивает деревья

Что запускает рендер, как React сравнивает деревья и зачем нужны key.

Рендер в React это вызов функции

«Рендер» в React не означает «перерисовать экран». Это вызов функции компонента, которая возвращает описание интерфейса (React-элементы). Дальше React сам решает, что поменять в DOM.

Работа идёт в два этапа:

  1. Render phase. React вызывает компоненты и получает новое дерево элементов. Сравнивает его с предыдущим. DOM не трогается, этап можно прервать и повторить.
  2. Commit phase. React применяет найденные изменения к DOM, обновляет ref, затем запускает эффекты (useLayoutEffect синхронно, useEffect после отрисовки).

Отсюда главное правило: функция компонента должна быть чистой. Одинаковые props, state и context дают одинаковый JSX, без побочных эффектов во время рендера.

Что запускает рендер

  • Первый монтаж (root.render(<App />)).
  • Обновление state этого компонента (setState, dispatch).
  • Рендер родителя: по умолчанию все дети рендерятся заново, даже если их props не изменились.
  • Изменение значения Context, на который компонент подписан.

Изменение props само по себе ничего не запускает: props меняются только потому, что отрендерился родитель.

Если в setState передать то же значение (сравнение через Object.is), React пропустит обновление. Поэтому мутация объекта не работает:

const [user, setUser] = useState({ name: 'Аня' });
user.name = 'Оля';
setUser(user); // та же ссылка, экран не обновится
setUser({ ...user, name: 'Оля' }); // новая ссылка, обновится

Reconciliation: как React сравнивает деревья

Полное сравнение двух деревьев стоило бы O(n³). React использует эвристику за O(n):

  • Разный тип элемента на той же позиции (div сменился на section, Profile на Settings) означает: старое поддерево размонтируется целиком вместе со state, новое монтируется с нуля.
  • Тот же тип означает: React обновляет атрибуты DOM-узла или передаёт новые props тому же экземпляру компонента, state сохраняется.
  • Списки сравниваются по key.

State привязан не к компоненту «вообще», а к позиции в дереве. Одинаковый компонент на той же позиции сохраняет state, даже если вы думаете о нём как о «другом».

{isAdmin ? <Counter /> : <Counter />} // одна позиция, один тип: state общий

key

key помогает React сопоставить элементы списка между рендерами. Ключ должен быть стабильным и уникальным среди соседей, обычно это id из данных.

Индекс как ключ ломается, когда список меняет порядок, в начало вставляют элемент или удаляют из середины: state и DOM (например, текст в неконтролируемом input) «переезжают» к соседним записям.

key можно использовать и намеренно: смена ключа заставляет React пересоздать компонент со свежим state.

<ChatRoom key={roomId} roomId={roomId} />

Частые ошибки

Компонент, объявленный внутри компонента. На каждый рендер родителя создаётся новая функция, то есть новый тип. React размонтирует и монтирует его заново, state и фокус теряются.

function Form() {
  function Field() { return <input />; } // плохо: новый тип каждый рендер
  return <Field />;
}

Ноль в условном рендере. {count && <Badge />} при count = 0 выведет 0, потому что 0 валидный дочерний элемент. Пишите {count > 0 && <Badge />}.

Побочные эффекты в теле компонента. Запросы, подписки, запись во внешние переменные в рендере. В StrictMode в режиме разработки React вызывает компонент дважды, чтобы такие ошибки проявились.

Что спросят на собесе

  • Что запускает рендер компонента и чем рендер отличается от коммита.
  • Как работает reconciliation и что происходит при смене типа элемента.
  • Зачем нужен key, почему индекс опасен и как сбросить state через key.
  • Почему рендерятся дети, у которых не поменялись props.
  • Зачем StrictMode вызывает компоненты дважды.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.