React — виртуальный DOM и reconciliation
Virtual DOM (VDOM) — JavaScript-представление дерева DOM в памяти. Reconciliation — алгоритм React, который сравнивает новый VDOM со старым (diffing) и вычисляет минимальный набор реальных DOM-операций для обновления UI.
Зачем нужно
Прямые манипуляции с реальным DOM дорогостоящи — каждое изменение может вызвать reflow и repaint. React батчит обновления, находит только изменившиеся узлы и применяет минимум операций к реальному DOM. Понимание reconciliation помогает писать перформантный код и избегать ненужных ре-рендеров.
Где используется
- Оптимизация компонентов с частыми обновлениями состояния
- Понимание причин ненужных ре-рендеров (для использования
React.memo,useMemo) - Правильное использование
keyprop в списках
Основной контент
Как работает reconciliation
1. Триггер: setState / useState setter / props change
2. Рендер: React вызывает функцию компонента → новый VDOM
3. Diffing: сравнение нового VDOM со старым (O(n), не O(n³))
4. Commit: применение изменений к реальному DOM
Алгоритм эвристики:
- Разные типы элементов → полное перестроение поддерева
- Одинаковый тип → обновление атрибутов
- Массивы → сравнение по key prop
Пример: почему key важен
// ПЛОХО — key по индексу (перемешивание → полный перерендер)
{items.map((item, index) => (
<ListItem key={index} item={item} />
))}
// ХОРОШО — key по стабильному уникальному идентификатору
{items.map(item => (
<ListItem key={item.id} item={item} />
))}
// Почему это важно:
// При удалении первого элемента с key=index:
// - React думает, что изменились ВСЕ последующие элементы
// - Перерендеривает всё дерево
// С key=item.id React корректно сопоставляет элементы
Батчинг обновлений (React 18)
// React 18: все обновления батчатся автоматически
function handleClick() {
setCount(c => c + 1); // Не вызывает ре-рендер сразу
setActive(true); // Не вызывает ре-рендер сразу
// Один ре-рендер с обоими обновлениями
}
// React 17 и ниже: батчинг только в event handlers
// Async обновления не батчились автоматически
setTimeout(() => {
setCount(c => c + 1); // Ре-рендер!
setActive(true); // Ещё ре-рендер! (2 рендера вместо 1)
}, 0);
Fiber — архитектура React 16+
Fiber — структура данных для каждого компонента.
Позволяет прерывать и возобновлять reconciliation.
Две фазы:
Render phase — прерываемая, вычисляет изменения (без side effects)
Commit phase — синхронная, применяет изменения к DOM
Приоритеты (React Concurrent Mode):
Срочные (urgent): клики, ввод текста → немедленно
Несрочные (transition): поиск, фильтрация → могут прерваться
// startTransition — пометить обновление как несрочное
import { startTransition } from 'react';
function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState();
function handleChange(e) {
setQuery(e.target.value); // Срочное — UI должен отвечать сразу
startTransition(() => {
setResults(heavySearch(e.target.value)); // Несрочное — можно прервать
});
}
return <input value={query} onChange={handleChange} />;
}
Частые ошибки
- Использование индекса массива как
keyдля изменяемых списков - Создание новых объектов/функций в render без
useMemo/useCallback→ ненужные ре-рендеры - Слишком глубокое дерево компонентов — ре-рендер наверху перерендеривает всё дерево
- Изменение state в render (бесконечный цикл)