Как React сравнивает 2 узла?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как React сравнивает узлы в Virtual DOM
Проблема в наивном подходе
Если бы React сравнивал весь DOM буквально (каждое изменение), это было бы медленно:
// Наивно: сравнить полностью - O(n^3) сложность
const oldTree = renderComponent();
const newTree = renderComponent();
const diff = compare(oldTree, newTree);
Instead, React использует эвристические алгоритмы с O(n) сложностью.
React Reconciliation Algorithm (Алгоритм согласования)
React сравнивает узлы по следующим правилам:
1. Сравнение по типу элемента
// Если тип изменился - React пересоздаёт весь компонент
// Было:
<MyComponent />
// Стало:
<OtherComponent />
// Результат: старый компонент уничтожен, новый создан заново
// (даже если содержимое внутри похоже)
2. Сравнение по ключам (keys)
Для списков React использует key как явный идентификатор:
// ПЛОХО - без ключей
{items.map((item, index) => (
<div>{item.name}</div> // index может измениться!
))}
// ХОРОШО - с уникальным ключом
{items.map(item => (
<div key={item.id}>{item.name}</div> // id никогда не меняется
))}
Без ключей React сравнивает по позиции в массиве, что приводит к ошибкам.
Алгоритм сравнения (The Diffing Algorithm)
Когда React рендерит компонент дважды:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
// Рендер 1: count = 0
const oldVDOM = {
type: 'div',
children: [
{ type: 'p', children: 'Count: 0' },
{ type: 'button', children: '+' }
]
};
// Рендер 2: count = 1
const newVDOM = {
type: 'div',
children: [
{ type: 'p', children: 'Count: 1' }, // изменилось
{ type: 'button', children: '+' } // не изменилось
]
};
// React обновляет только содержимое <p>
Правила сравнения
Правило 1: Разные типы элементов
if (oldVNode.type !== newVNode.type) {
// Уничтожить старое, создать новое
removeNode(oldNode);
createNode(newNode);
}
Правило 2: Одинаковые типы - сравнить пропсы
if (oldVNode.type === newVNode.type) {
// Сравнить props
if (oldVNode.props !== newVNode.props) {
updateNodeProps(node, oldVNode.props, newVNode.props);
}
// Рекурсивно сравнить детей
reconcileChildren(oldVNode.children, newVNode.children);
}
Правило 3: Сравнение детей по ключам
function reconcileChildren(oldChildren, newChildren) {
const oldByKey = {};
const newByKey = {};
// Индексировать старых детей по ключу
oldChildren.forEach(child => {
oldByKey[child.key] = child;
});
newChildren.forEach(child => {
newByKey[child.key] = child;
if (oldByKey[child.key]) {
// Ключ есть и в старом, и в новом - обновить
reconcile(oldByKey[child.key], child);
} else {
// Новый элемент - создать
create(child);
}
});
// Элементы, которые только в старом - удалить
Object.values(oldByKey).forEach(child => {
if (!newByKey[child.key]) {
remove(child);
}
});
}
Практический пример: список с фильтром
function TodoList() {
const [todos, setTodos] = useState([
{id: 1, text: 'Learn React'},
{id: 2, text: 'Build app'}
]);
// БЕЗ ключей - ПРОБЛЕМА
return (
<ul>
{todos.map((todo, index) => (
<li key={index}> {/* ПЛОХО! */}
<input type="checkbox" />
{todo.text}
</li>
))}
</ul>
);
}
// Если удалить первый элемент, React подумает:
// "Второй элемент переместился на первую позицию,
// поэтому я просто изменю текст и обновлю checkbox"
// Но checkbox state останется у НОВОГО первого элемента!
// ПРАВИЛЬНО:
return (
<ul>
{todos.map(todo => (
<li key={todo.id}> {/* ХОРОШО */}
<input type="checkbox" />
{todo.text}
</li>
))}
</ul>
);
// Теперь React знает, что это разные элементы,
// и корректно удалит старый и переместит states
React.memo - Оптимизация на уровне компонента
Если компонент получает одинаковые пропсы - не рендерить:
const UserCard = React.memo(({user}) => (
<div>{user.name}</div>
));
// React сравнит пропсы
// Если {user} === oldProps.user - не рендерить
// Если {user} !== oldProps.user - рендерить
Вывод
React использует умные эвристики для сравнения:
- Сравнение по типу элемента
- Сравнение по ключам для списков
- Сравнение пропсов для обновления
Использование правильных ключей критично для корректной работы React!