Как происходит оптимизация с виртуальным деревом в React
Что проверяют
Понимаете ли вы, что виртуальное дерево не ускоряет всё само, а служит способом посчитать минимальный набор изменений реального DOM. Уточнение обычно про key в списках или про то, когда React.memo не помогает.
Как отвечать
Сначала механика:
- элементы: JSX превращается в обычные объекты с типом, пропсами и детьми. Рендер компонента это вызов функции, которая возвращает новое дерево таких объектов;
- reconciliation: React сравнивает новое дерево с предыдущим. Точное сравнение деревьев стоит O(n³), поэтому используется эвристика за O(n) на двух допущениях: элементы разного типа дают разные поддеревья, а дети в списке опознаются по
key; - две фазы: в фазе рендера React вычисляет изменения, её можно прервать и повторить (архитектура Fiber, конкурентный рендеринг). В фазе коммита изменения синхронно применяются к DOM, затем запускаются эффекты;
- батчинг: с React 18 несколько
setStateподряд объединяются в один рендер, даже в промисах и таймерах.
Потом инструменты, которые уменьшают работу:
React.memo,useMemo,useCallback: пропускают рендер, если пропсы не изменились поObject.is. Эту мемоизацию умеет расставлять React Compiler;- стабильные ключи: id из данных, а не индекс;
- состояние пониже: чем глубже живёт стейт, тем меньше компонентов перерисовывается;
useTransitionиuseDeferredValue: помечают тяжёлое обновление как несрочное, ввод остаётся отзывчивым;- виртуализация списков: react-window или TanStack Virtual рендерят только видимые строки.
Узкие места ищите в Profiler из React DevTools.
Где спотыкаются
Говорят, что виртуальный DOM «быстрее реального». Сравнение деревьев это дополнительная работа, а выигрыш дают меньшее число операций с DOM и удобство разработки.
Ставят индекс в key у списка, который сортируется или фильтруется. React переиспользует не тот узел, и значение инпута переезжает в чужую строку.
Оборачивают всё в React.memo, но передают инлайн-объекты и стрелочные функции. Пропсы каждый раз новые, мемоизация не срабатывает, а сравнение тратит время. И не называйте главным инструментом shouldComponentUpdate: это API классовых компонентов.