Рендер в React это вызов функции
«Рендер» в React не означает «перерисовать экран». Это вызов функции компонента, которая возвращает описание интерфейса (React-элементы). Дальше React сам решает, что поменять в DOM.
Работа идёт в два этапа:
- Render phase. React вызывает компоненты и получает новое дерево элементов. Сравнивает его с предыдущим. DOM не трогается, этап можно прервать и повторить.
- 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 вызывает компоненты дважды.