← Назад к вопросам
Что происходит при сверке двух DOM в reconciliation?
2.0 Middle🔥 222 комментариев
#JavaScript Core
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что происходит при сверке двух DOM в reconciliation
Это один из ключевых вопросов про React. Reconciliation (согласование/ревизия) — это процесс, при котором React определяет, какие элементы в Virtual DOM изменились, и как эффективно обновить реальный DOM. Это основа производительности React.
1. Что такое Reconciliation
Когда компонент меняет state или получает новые props:
function Counter() {
const [count, setCount] = useState(0);
// При клике вызывается setCount
// React создаёт НОВЫЙ Virtual DOM
// Сравнивает новый Virtual DOM со старым
// Обновляет только изменённые части реального DOM
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
2. Алгоритм сравнения: Diffing
React использует эффективный алгоритм O(n) вместо O(n^3):
Правило 1: Разные типы элементов
// Старый Virtual DOM
const oldVdom = <div className="container"><p>Hello</p></div>;
// Новый Virtual DOM - ДРУГОЙ тип элемента
const newVdom = <section className="container"><p>Hello</p></section>;
// React сделает:
// 1. Удалит весь старый DOM (div и его детей)
// 2. Создаст новый DOM (section и её детей)
// Это потому, что div и section - разные элементы, переиспользовать нельзя
// Пример с компонентами
const oldVdom = <UserProfile name="John" />;
const newVdom = <AdminProfile name="John" />; // Другой компонент
// React:
// 1. Размонтирует UserProfile
// 2. Смонтирует AdminProfile
// Instance изменился - состояние и refs будут потеряны
Правило 2: Одинаковый тип элемента
// Старый Virtual DOM
const oldVdom = <div className="old-class" id="my-div" style={{color: 'red'}}>Text</div>;
// Новый Virtual DOM
const newVdom = <div className="new-class" id="my-div" style={{color: 'blue'}}>Text</div>;
// React сделает:
// 1. Сохранит DOM элемент (это всё ещё div)
// 2. Обновит только изменённые атрибуты:
// - className: "old-class" -> "new-class" (изменился)
// - id: "my-div" -> "my-div" (не изменился, не трогаем)
// - style.color: "red" -> "blue" (изменился)
// - innerText: "Text" -> "Text" (не изменился)
// В итоге обновляет только className и style.color
Правило 3: Lists и Keys
Это самая важная часть!
// БЕЗ key (ПЛОХО)
function UserList({ users }) {
return (
<ul>
{users.map(user => ( // index как key по умолчанию
<li>{user.name}</li>
))}
</ul>
);
}
// Старый список: [Alice, Bob, Charlie]
// <li>Alice</li>
// <li>Bob</li>
// <li>Charlie</li>
// Новый список: [Alice, Bob, David, Charlie]
// React думает что произошло:
// <li>Alice</li> -> остаётся (index 0)
// <li>Bob</li> -> остаётся (index 1)
// <li>David</li> -> создаёт новый (index 2) ЭТО НЕПРАВИЛЬНО!
// <li>Charlie</li> -> обновляет (index 3)
// Результат: неправильная сверка, возможны баги
// С key (ХОРОШО)
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li> // Используем уникальный id
))}
</ul>
);
}
// React теперь знает:
// key="alice-1" осталась на месте
// key="bob-1" осталась на месте
// key="david-1" добавлена (новая)
// key="charlie-1" осталась (просто переместилась)
// Правильная сверка, правильный результат
3. Этапы Reconciliation
// Этап 1: Render Phase (чистый, можно отменить)
function MyComponent({ value }) {
// Здесь React создаёт новый Virtual DOM
// Это очень быстро, ничего не меняется в DOM
console.log('Render phase');
return <div>{value}</div>;
}
// Этап 2: Commit Phase (необратимо меняет DOM)
// React обновляет реальный DOM
// Вызывает lifecycle методы (useEffect, componentDidMount и т.д.)
// В это время браузер не может рендерить
// Итоговый порядок:
// 1. setState() -> React начинает работу
// 2. [Render Phase] -> создаёт новый Virtual DOM
// 3. [Commit Phase] -> обновляет реальный DOM
// 4. [Layout] -> браузер пересчитывает layout
// 5. [Paint] -> браузер рисует пиксели на экран
4. Практический пример
function App() {
const [items, setItems] = useState([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
]);
const handleAddItem = () => {
// setState вызывает reconciliation
setItems([...items, { id: 3, name: 'Item 3' }]);
};
return (
<div>
<button onClick={handleAddItem}>Add Item</button>
<ul>
{items.map(item => (
<ItemComponent key={item.id} item={item} />
))}
</ul>
</div>
);
}
function ItemComponent({ item }) {
console.log(`Rendering item ${item.id}`);
return (
<li>
{item.name}
<input type="checkbox" /> {/* Этот input сохранится при reconciliation */}
</li>
);
}
// При клике на кнопку:
// Render phase:
// - React видит: items теперь массив с 3 элементами
// - Создаёт новый Virtual DOM
// - Сравнивает с предыдущим
// - Видит: первые 2 элемента не изменились, 3-й новый
//
// Commit phase:
// - Обновляет реальный DOM
// - Добавляет новый <li> элемент
// - Существующие <li> элементы остаются (с их input значениями!)
5. Performance оптимизации в reconciliation
// 1. React.memo - предотвращение ненужных re-renders
const UserCard = React.memo(({ user }) => {
console.log('Rendering UserCard for', user.id);
return <div>{user.name}</div>;
});
// UserCard рендерится только если user пропс изменился
// 2. useMemo - мемоизация значений
function Parent() {
const [count, setCount] = useState(0);
const [data, setData] = useState({ id: 1, name: 'John' });
const memoizedData = useMemo(() => data, [data]);
return <Child data={memoizedData} />;
}
// Child не перендерится если data не изменилась
// 3. useCallback - мемоизация функций
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log(count);
}, [count]);
return <Child onClick={handleClick} />;
}
// 4. Правильная структура state
// ПЛОХО - большой объект
const [state, setState] = useState({
user: { id: 1, name: 'John' },
posts: [{ id: 1, title: 'Post 1' }],
theme: 'dark',
});
// ХОРОШО - разделённый state
const [user, setUser] = useState({ id: 1, name: 'John' });
const [posts, setPosts] = useState([{ id: 1, title: 'Post 1' }]);
const [theme, setTheme] = useState('dark');
// Так компоненты перендерятся только если их данные изменились
6. Визуализация процесса
Олд Virtual DOM Новый Virtual DOM Реальный DOM
+------+ +------+ +------+
| div | | div | | div |
| id=1| vs | id=1| | |
| cls=a| | cls=b| -> | cls=b| <- обновлено
| +---+ | | +---+ | | +---+ |
| | p | | | | p | | | | p | |
| | txt| | | | new| | | |new | | <- изменено
| +---+ | | +---+ | | +---+ |
+------+ +------+ +------+
Когда React создаёт новый Virtual DOM:
1. Видит: оба корневые элементы это <div> (тип одинаков)
2. Сравнивает props: id осталась, cls изменилась
3. Обновляет cls в реальном DOM
4. Смотрит детей: оба <p> (тип одинаков)
5. Сравнивает содержимое: текст изменился
6. Обновляет текст в реальном DOM
7. Всё, reconciliation завершён
Ответ на интервью
«При reconciliation React:
- Сравнивает типы элементов: если тип изменился (div -> section), удаляет старый DOM и создаёт новый
- Если тип одинаков: сравнивает props и обновляет только изменённые атрибуты
- Для списков: использует key для идентификации элементов и корректного обновления
- Diffing алгоритм O(n): это позволяет React быстро определить изменения
- Не трогает DOM: если элемент не изменился, React не трогает его в реальном DOM
Ключевое правило: всегда используй unique key для списков, не используй index как key, так как это может привести к неправильной сверке при добавлении/удалении элементов.
Визуально: старый Virtual DOM -> новый Virtual DOM -> только изменённые части обновляются в реальном DOM.»