Как работает рендеринг и реконсиляция
Урок объясняет, что именно происходит, когда React «перерисовывает» компонент, и почему рендер — это не то же самое, что обновление DOM.
Рендер — это вызов функции компонента, который возвращает описание UI (дерево элементов). React сравнивает новое описание со старым и точечно правит реальный DOM.
Две фазы: render и commit
React работает в два этапа. На фазе render он вызывает ваши компоненты-функции и получает дерево React-элементов — это чистые JS-объекты, ещё не DOM. На фазе commit React применяет минимальные изменения к настоящему DOM. Важно понять: вызов функции компонента (render) не равен изменению DOM. Компонент может перерисоваться десятки раз, а DOM при этом не тронуться, если результат не изменился.
Компонент перерисовывается в трёх случаях: изменилось его собственное состояние (useState/useReducer), изменился контекст, который он читает, или перерисовался его родитель. Последний пункт — источник большинства недоразумений: по умолчанию, если родитель рендерится, рендерятся и все его дети, независимо от того, изменились их пропсы или нет.
Реконсиляция: как React находит изменения
Сравнение старого и нового дерева элементов называется реконсиляцией (reconciliation). React не сравнивает деревья «как есть» — это было бы слишком дорого. Вместо этого он использует эвристики:
- Элементы разного типа (
<div>против<span>) считаются полностью разными — старое поддерево уничтожается, новое строится с нуля. - Элементы одного типа сравниваются по пропсам, и меняются только отличающиеся атрибуты.
- Списки детей сопоставляются по
key— об этом отдельный урок.
Пример: рендер ≠ перерисовка DOM
Рассмотрим JSX. Компонент Parent хранит счётчик. При каждом клике Parent рендерится заново, и вместе с ним вызывается функция Child — хотя в DOM ребёнка ничего не меняется.
function Child() {
console.log("Child рендерится");
return <p>Я статичный ребёнок</p>;
}
function Parent() {
const [count, setCount] = React.useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Клик: {count}</button>
<Child />
</div>
);
}
Каждый клик пишет в консоль «Child рендерится», хотя текст <p> неизменен. Render выполняется, а commit для Child — нет (React видит, что результат тот же). Это нормально и обычно дёшево. Проблемой это становится, когда рендер ребёнка тяжёлый — тогда его стоит мемоизировать (следующие уроки).
Главное правило: не оптимизируйте вслепую
Сам по себе лишний вызов функции компонента — копейки. React спроектирован так, чтобы рендеры были дешёвыми. Преждевременная оптимизация рендеров усложняет код и часто ничего не ускоряет. Сначала измеряйте (Profiler), потом оптимизируйте конкретное узкое место.
Итог
- Render — вызов функции компонента; commit — применение изменений к DOM. Это разные вещи.
- Перерисовка ребёнка из-за рендера родителя — норма, а не баг.
- Реконсиляция сравнивает деревья по типу элемента и по
key. - Не оптимизируйте, пока не измерили. Дешёвый рендер оптимизировать незачем.