Как в React происходит согласование деревьев?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как в React происходит согласование деревьев (Reconciliation)
Это один из самых важных механизмов React. Когда состояние меняется, React должен эффективно обновить DOM. Давай разберемся в деталях.
Что такое согласование
Reconciliation - процесс сравнения старого Virtual DOM с новым Virtual DOM и обновления реального DOM только там, где что-то изменилось.
// Компонент меняет состояние
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
// Когда пользователь кликает:
// 1. setCount(1) -> trigger re-render
// 2. React создает новое Virtual DOM дерево
// 3. Сравнивает со старым деревом
// 4. Обновляет только <p>Count: 1</p> в реальном DOM
Алгоритм Diffing
React использует эвристический алгоритм O(n) вместо O(n^3):
Эвристика 1: Different element types
// Если тип элемента изменился, React пересоздает дерево целиком
// Было
<div>
<Counter />
</div>
// Стало
<span>
<Counter />
</span>
// React скажет: "<div> стал <span>, нужно пересоздать все"
// Старый Counter уничтожен, новый создан заново
Эвристика 2: Attributes
// Было
<div className="red">Hello</div>
// Стало
<div className="blue">Hello</div>
// React обновит только className, остальное не трогает
Эвристика 3: Children with Keys
// БЕЗ key (плохо)
<ul>
{items.map(item => <li>{item.name}</li>)}
</ul>
// Если добавить item в начало - React пересоздаст ВСЕ li!
// Потому что не знает какой li какому item соответствует
// С key (хорошо)
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
// Теперь React знает: li с key=3 это всегда item с id=3
// Добавляет новый item -> добавляет новый li, старые не трогает
Жизненный цикл Reconciliation
Фаза 1: Render Phase (можно прерваться)
// React вызывает функцию компонента
function MyComponent() {
return <div>...</div>; // <- это вызов
}
// Результат - новое Virtual DOM дерево
// На этой фазе нет побочных эффектов (useEffect еще не запущен)
// Эта фаза может быть запущена несколько раз
Фаза 2: Commit Phase (гарантированное выполнение)
// React применяет изменения к реальному DOM
// 1. Обновляет DOM элементы
// 2. Запускает useLayoutEffect (синхронно)
// 3. Браузер перерисовывает (paint)
// 4. Запускает useEffect (асинхронно)
Virtual DOM и реальный DOM
// Virtual DOM - это просто объект JavaScript
const vdom = {
type: 'div',
props: {
className: 'container',
children: [
{
type: 'h1',
props: { children: 'Title' }
},
{
type: 'p',
props: { children: 'Content' }
}
]
}
};
// React преобразует это в реальный DOM:
<div className="container">
<h1>Title</h1>
<p>Content</p>
</div>
Optimization: Memoization
Если пересчет компонента дорогой, используй React.memo:
// Компонент будет пересчитан, даже если props не изменились
function BigList({ items }) {
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
// Обернуть в memo
const BigListMemo = React.memo(BigList);
// Теперь если items массив the same (по ссылке) - компонент не пересчитывается
// Но ВНИМАНИЕ! Нужно убедиться что props стабильны
function Parent() {
const items = [1, 2, 3]; // создается каждый раз!
return <BigListMemo items={items} />; // бесполезно, items всегда "новый"
}
// Правильно
function Parent() {
const items = useMemo(() => [1, 2, 3], []); // один раз
return <BigListMemo items={items} />;
}
Батчинг (Batching)
React группирует несколько setState в один re-render:
function handleClick() {
setCount(count + 1); // <- не перерендер
setTitle('Clicked'); // <- не перерендер
setActive(true); // <- не перерендер
} // <- только ОДИН перерендер здесь
// В React 18+ это работает везде (раньше только в обработчиках)
setTimeout(() => {
setCount(count + 1); // <- батчирован
setTitle('Later'); // <- батчирован
}, 1000); // <- один перерендер
Когда перерендер неизбежен
// Компонент перерендерится если:
// 1. Его props изменились
function Card({ title }) {
return <h1>{title}</h1>; // перерендер если title изменился
}
// 2. Его state изменился
function Counter() {
const [count, setCount] = useState(0); // перерендер если count изменился
return <div>{count}</div>;
}
// 3. Контекст изменился
function Profile() {
const user = useContext(UserContext); // перерендер если user изменился
return <div>{user.name}</div>;
}
// 4. Родитель перерендерился (если не используешь memo)
function Child() {
return <div>Child</div>; // перерендер вместе с родителем
}
Ключевые insights
-
Virtual DOM это не "быстрее" чем реальный DOM
- Это просто инструмент для эффективного обновления
-
Keys критичны для списков
- Без keys React может обновить не те элементы
- Используй стабильный ID, не индекс
-
Reconciliation это дорого
- Минимизируй перерендеры с помощью memo, useMemo
- Но не переусложняй код ради оптимизации
-
Render и Commit фазы разные
- Render может быть прерван
- Commit всегда завершается
Профилирование
// DevTools Profiler показывает:
// - Какие компоненты перерендерились
// - Сколько времени потребовалось
// - Почему произошел перерендер
// Используй для поиска узких мест
Вывод
Reconciliation - это оптимизированный процесс обновления DOM. React:
- Сравнивает старое и новое Virtual DOM деревья
- Определяет минимальные изменения
- Обновляет реальный DOM
- Запускает побочные эффекты
Понимание этого процесса критично для написания производительного React кода.