← Назад к вопросам

Как React сравнивает 2 узла?

2.0 Middle🔥 111 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как 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!