В чем особенность алгоритма diffing и почему React работает быстро
Что проверяют
Понимаете ли вы, какие допущения делает reconciliation и почему из них вытекают правила про key и стабильные типы компонентов. Следом часто спрашивают, правда ли виртуальный DOM быстрее прямой работы с DOM и что изменил Fiber.
Как отвечать
При обновлении состояния React заново вызывает рендер компонентов и получает новое дерево элементов. Сравнивает он его с предыдущим деревом элементов, а не с реальным DOM. Точный поиск минимальной разницы между двумя деревьями стоит порядка O(n³), поэтому React использует эвристики и укладывается в O(n):
- разный тип, разное дерево: если на месте
<div>появился<section>или другой компонент, React не сравнивает детей, а размонтирует старое поддерево целиком, со всем состоянием, и строит новое; - сравнение по уровням: узлы сопоставляются только на одной глубине, перенос поддерева на другой уровень выглядит как удаление и создание;
- ключи в списках:
keyговорит, какой элемент какому соответствует. Со стабильными ключами вставка в начало списка даёт одну вставку в DOM вместо переписывания всех элементов.
Дальше про архитектуру. С React 16 работа идёт на Fiber: фаза render разбита на единицы, её можно прервать и отдать приоритет вводу пользователя, на этом держатся startTransition и конкурентный рендеринг из React 18. Фаза commit применяет накопленные изменения к DOM за один проход.
Отсюда честный ответ на «почему быстро»: React не быстрее аккуратного ручного кода, но изменения DOM он делает минимальными и пакетными, а декларативная модель избавляет от ручного управления ими. Основная стоимость переносится в JS, в сам рендер компонентов, и её режут React.memo, useMemo или React Compiler, который расставляет мемоизацию автоматически.
Где спотыкаются
Говорят, что виртуальный DOM сравнивается с реальным. Реальный DOM при сравнении React не читает.
Используют индекс массива как key в списке, который сортируется или фильтруется. Состояние полей и фокус начинают «переезжать» между элементами.
Объявляют компонент внутри другого компонента. На каждом рендере это новый тип, поэтому поддерево размонтируется, теряет состояние, а эффекты перезапускаются.