Как работает reconciliation в React?
Что такое reconciliation
Reconciliation (согласование) — это механизм React, определяющий, какие изменения нужно внести в реальный DOM после перерендера компонента. React не обновляет DOM напрямую при каждом вызове setState или изменении пропсов, а сначала строит новое дерево виртуального DOM и сравнивает его с предыдущим.
Алгоритм диффинга
Наивное сравнение двух деревьев имеет сложность O(n³), что неприемлемо для UI. React использует эвристический алгоритм со сложностью O(n), основанный на двух ключевых предположениях:
- Элементы разных типов производят разные деревья — если тип корневого элемента изменился (например,
<div>→<span>), React уничтожает всё старое поддерево и строит новое с нуля. - Ключи (
key) помогают идентифицировать стабильные элементы — при рендере списков React используетkeyдля сопоставления элементов между рендерами.
Этапы сравнения
Сравнение по типу
Если тип элемента совпадает — React обновляет только изменившиеся атрибуты/пропсы. Если тип изменился — старый узел уничтожается (componentWillUnmount, очистка эффектов), монтируется новый (componentDidMount, эффекты).
// React переиспользует DOM-узел <input>, обновив только className
// До: <input className="old" />
// После: <input className="new" />
Reconciliation списков
// Без key React может перемонтировать все элементы при добавлении в начало
const BadList = ({ items }: { items: string[] }) => (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li> // Плохо: индекс как key при изменяемом порядке
))}
</ul>
);
const GoodList = ({ items }: { items: { id: number; name: string }[] }) => (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li> // Хорошо: стабильный уникальный key
))}
</ul>
);
React Fiber
С версии React 16 reconciliation реализован через архитектуру Fiber. Каждый элемент дерева представлен объектом Fiber — связным списком, позволяющим прерывать и возобновлять работу.
Работа делится на две фазы:
- Render-фаза (reconciliation) — чистая, без побочных эффектов, может быть прервана планировщиком (Scheduler). React строит «work-in-progress» дерево.
- Commit-фаза — синхронная, применяет изменения к реальному DOM и запускает эффекты (
useEffect,useLayoutEffect).
// Пример: почему render может вызываться несколько раз в StrictMode
// React намеренно двойной вызов для выявления побочных эффектов в render-фазе
const Component = () => {
console.log('render'); // В StrictMode (dev) выведется дважды
return <div>Hello</div>;
};
Оптимизации
React.memo/PureComponent— пропускают reconciliation при неизменных пропсахuseMemo/useCallback— стабилизируют ссылки, предотвращая ненужные перерендеры дочерних компонентовkeyна нестабильных элементах — намеренный сброс состояния компонента
Что хочет услышать интервьюер
Понимание двухфазной модели: render-фаза (построение дерева, диффинг) и commit-фаза (применение к DOM)
Знание эвристик алгоритма O(n): сравнение по типу элемента и роль ключей
Понимание архитектуры Fiber и зачем она нужна (прерываемость, приоритеты задач)
Осознание последствий неправильного использования key (особенно индекс в динамических списках)
Умение связать reconciliation с практическими оптимизациями: memo, useMemo, useCallback
Пример: Влияние типа элемента на reconciliation
// Смена типа — React уничтожит Counter и потеряет его state
const App = ({ isAdmin }: { isAdmin: boolean }) => {
return isAdmin
? <div><Counter /></div> // Counter монтируется заново при каждом переключении
: <span><Counter /></span>;
};
// Сохранение state — одинаковый тип на одной позиции
const AppFixed = ({ isAdmin }: { isAdmin: boolean }) => {
return (
<div>
<Counter /> {/* state сохраняется — div не меняется */}
</div>
);
};
Пример: Key для принудительного сброса состояния
// Используем key для намеренного сброса state компонента
// без изменения его типа или позиции в дереве
const ProfilePage = ({ userId }: { userId: number }) => {
return (
<UserProfile
key={userId} // При смене userId React уничтожит старый и создаст новый
userId={userId}
/>
);
};
Типичные ошибки
Путают reconciliation с виртуальным DOM — Virtual DOM это структура данных, reconciliation — алгоритм работы с ней
Считают, что React всегда перерисовывает весь реальный DOM при каждом setState
Используют индекс массива как key в списках с изменяемым порядком элементов, не понимая последствий
Не знают о существовании двух фаз (render/commit) и не понимают, почему побочные эффекты нельзя делать в render-фазе
Путают понятия «перерендер компонента» и «обновление реального DOM» — первое не всегда влечёт второе


