Как работает алгоритм определения изменений в виртуальном DOM
Что проверяют
Понимаете ли вы, какие допущения делает reconciliation в React и почему от них зависят баги со стейтом. Следом почти всегда идёт вопрос про key в списках, иногда про Fiber и про то, нужен ли виртуальный DOM вообще.
Как отвечать
Точное сравнение двух произвольных деревьев стоит порядка O(n³), поэтому React использует эвристику за O(n) на двух допущениях:
- разный тип, разное дерево: если на месте
<div>появился<span>или другой компонент, React не сравнивает детей, а размонтирует поддерево целиком и монтирует новое. Состояние внутри теряется; - одинаковый DOM-тип: сравниваются пропсы, в реальный DOM уходят только изменённые атрибуты, затем рекурсия по детям;
- одинаковый компонент: экземпляр и его стейт сохраняются, компонент перерисовывается с новыми пропсами;
- списки: дети сопоставляются по
key. Без ключей сравнение идёт по индексу, и вставка в начало списка заставит обновить все элементы.
Затем разделите две фазы. Render-фаза вычисляет новое дерево и может прерываться: Fiber режет работу на единицы, а конкурентный рендеринг из React 18 даёт обновлениям приоритеты. Commit-фаза применяет изменения к DOM синхронно, за один проход.
Для полноты сравните с другими подходами. Vue 3 помогает диффу на этапе компиляции: статические узлы поднимаются, динамические помечаются флагами, а перестановки в списках с ключами минимизируются через наибольшую возрастающую подпоследовательность. Svelte и Solid обходятся без виртуального DOM и обновляют конкретные узлы через реактивность.
Где спотыкаются
Говорят, что виртуальный DOM «быстрее реального». Это дополнительная работа поверх DOM. Выигрыш есть только относительно наивной перерисовки всего, а ценность подхода в декларативной модели.
Ставят key={index} в список, который сортируется или фильтруется, или генерируют ключ через Math.random(). В первом случае стейт полей ввода «переезжает» к соседним строкам, во втором компоненты перемонтируются на каждом рендере.
Объявляют компонент внутри другого компонента. На каждом рендере это новая функция, то есть новый тип, и React размонтирует поддерево: теряется фокус и локальное состояние.