Как React решает, что перерисовать? Расскажите про согласование (reconciliation) и роль ключей в списках.
Короткий ответ
- Рендер — вызов компонентов и построение дерева элементов
- React сравнивает новое дерево с предыдущим (diffing)
- В DOM вносятся только найденные различия (commit)
- Разные типы элементов — поддерево пересоздаётся целиком
- Ключи сопоставляют элементы списка между рендерами
- Индекс как ключ ломает состояние при вставках и сортировке
- По умолчанию рендер родителя рендерит и детей
React строит новое дерево элементов, сравнивает его со старым по эвристикам (тип элемента и ключ) и точечно применяет различия к DOM, поэтому стабильные ключи критичны для списков.
Как сказать вслух
пример ответаПри изменении состояния React заново вызывает компонент и получает новое описание интерфейса — дерево элементов. Дальше он сравнивает его с прошлым деревом и применяет к странице только разницу, а не перерисовывает всё. Сравнение работает по простым правилам: если тип элемента сменился, поддерево строится заново, а элементы списков сопоставляются по ключам. Поэтому ключ должен быть стабильным идентификатором данных — с индексом массива при сортировке состояние элементов перепутается.
Подробный ответ
Основной ответ
Обновление в React состоит из двух фаз. В фазе рендера React вызывает функции компонентов и получает новое дерево элементов — лёгких объектов-описаний. Затем идёт согласование: новое дерево сравнивается с текущим. Полное сравнение деревьев слишком дорого, поэтому используются эвристики: элементы разных типов (div против span, другой компонент) не сравниваются — старое поддерево размонтируется вместе с состоянием, новое монтируется; элементы одного типа сохраняют DOM-узел, обновляются только изменившиеся атрибуты. Дети в списках сопоставляются по ключам: ключ говорит «это тот же элемент, что и раньше», позволяя переставлять узлы вместо пересоздания. В фазе коммита накопленные изменения применяются к DOM. Рендер родителя по умолчанию запускает рендер всех детей; это оптимизируют через React.memo, мемоизацию пропсов и правильную композицию (children).
Ключевые моменты
- Рендер не равно DOM. Вызов компонента — это дёшево; дорогие операции с DOM происходят только для реальных различий в коммите.
- Эвристика типов. Смена типа элемента уничтожает поддерево вместе со состоянием — поэтому нельзя объявлять компонент внутри компонента.
- Ключи в списках. Стабильный ключ из данных (id) сохраняет состояние и DOM элемента при перестановках; индекс безопасен только для статичных списков.
- Каскад рендеров. Ререндер идёт сверху вниз; memo и вынос состояния ниже по дереву ограничивают его зону.
Практический контекст
Это ядро понимания React: отсюда растут ответы про потерянное состояние инпутов в списках, лишние ререндеры и React.memo. На собеседовании часто дают баг: список с key={index}, при удалении элемента «едут» значения инпутов — нужно объяснить причину. В работе то же знание помогает читать профайлер React DevTools и находить, почему перерисовывается половина приложения.
Частые ошибки
- Считают, что «Virtual DOM быстрый» сам по себе, не понимая, что он просто экономит обращения к DOM
- Используют индекс массива как ключ в изменяемых списках
- Не знают, что смена типа элемента сбрасывает состояние всего поддерева