При каком условии дочернее дерево в React полностью перестраивается с нуля
Что проверяют
Знаете ли вы правила сверки, по которым React решает: обновить существующий узел или снести поддерево и смонтировать его заново. От этого зависит, сохранится ли состояние компонентов, и именно тут живут баги с потерянным вводом, слетающим фокусом и перезапущенными эффектами.
Как отвечать
Правило короткое: React сравнивает элементы по позиции в дереве, а решение принимает по паре «тип и ключ». Если на той же позиции оказался другой тип элемента или другая ссылка на функцию-компонент, старое поддерево размонтируется целиком вместе с состоянием, а новое создаётся с нуля. Совпал тип, значит узел обновляется на месте, а сверка идёт вглубь.
Второй случай это key. Изменившийся ключ на той же позиции равносилен другому компоненту, поддерево пересоздаётся. Этим пользуются намеренно, чтобы сбросить состояние формы при смене сущности:
<ProfileForm key={userId} userId={userId} />Третий случай встречается чаще, чем хотелось бы: компонент, объявленный внутри рендера другого. На каждый рендер получается новая функция, то есть новый тип, и потомок размонтируется. Сюда же относится обёртка в HOC в теле рендера.
Полезно добавить, что переезд элемента к другому родителю или под другую ветку условия тоже считается новой позицией, а ошибка при рендере размонтирует всё поддерево под ближайшей границей ошибок.
Где спотыкаются
Первое: путают повторный рендер и перемонтирование. Обновление родителя запускает рендер детей, но состояние при этом сохраняется, узлы DOM переиспользуются, эффекты без изменившихся зависимостей не перезапускаются. Формулировка «дерево строится заново» про обычный ререндер неверна.
Второе: индекс вместо ключа. При вставке в начало или сортировке ключи съезжают, React обновляет не те узлы, и состояние переезжает к чужим данным: введённое значение остаётся от прежней строки, анимация проигрывается не там. В неизменяемом списке индекс допустим, в изменяемом это ошибка.
Третье: думают, что React.memo управляет монтированием. Он лишь пропускает лишние рендеры при равных пропсах и от пересоздания поддерева не спасает: если сменился тип или ключ, компонент размонтируется вместе со всем своим состоянием и подписками.