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

Как React определяет, какие DOM узлы нужно менять?

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

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

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

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

Как React определяет, какие DOM узлы нужно менять?

Это один из самых важных вопросов для понимания того, как React работает под капотом. Ответ связан с концепцией Virtual DOM и Reconciliation Algorithm (алгоритм согласования).

Виртуальный DOM (Virtual DOM)

React не работает с реальным DOM напрямую. Вместо этого он:

  1. Создаёт виртуальную копию DOM (JavaScript объекты)
  2. При изменении состояния создаёт новый Virtual DOM
  3. Сравнивает старый и новый Virtual DOM (diffing)
  4. Обновляет только измённые элементы в реальном DOM (reconciliation)

Этот подход намного быстрее, чем обновлять реальный DOM напрямую, потому что операции с объектами JavaScript очень быстрые, а операции с DOM медленные.

// React Virtual DOM выглядит примерно так:
const vdom = {
  type: 'div',
  props: { className: 'container', children: [
    { type: 'h1', props: { children: 'Hello' } },
    { type: 'p', props: { children: 'World' } }
  ] }
};

// Реальный DOM:
// <div class="container">
//   <h1>Hello</h1>
//   <p>World</p>
// </div>

Алгоритм Diffing (сравнение)

Когда React нужно обновить UI, он:

  1. Сравнивает старое дерево Virtual DOM с новым
  2. Находит различия (diff)
  3. Создаёт минимальный набор изменений для реального DOM

Этот процесс называется reconciliation (согласование).

// Было (старый Virtual DOM):
const oldVdom = {
  type: 'div',
  props: {
    children: [
      { type: 'p', props: { children: 'Old text' } }
    ]
  }
};

// Стало (новый Virtual DOM):
const newVdom = {
  type: 'div',
  props: {
    children: [
      { type: 'p', props: { children: 'New text' } }
    ]
  }
};

// Diff (что изменилось):
// p.children: 'Old text' -> 'New text'

// React обновит только textContent элемента <p>

Правила Diffing

1. Сравнение элементов одного уровня (не рекурсивно)

React сравнивает только элементы на одном уровне дерева:

// Было:
<div>
  <Button />
  <Input />
</div>

// Стало:
<div>
  <Input />
  <Button />
</div>

// React НЕ поменяет местами Button и Input
// Вместо этого он пересоздаст оба элемента!
// Решение: используй key
<div>
  <Button key="btn" />
  <Input key="input" />
</div>

2. Разные типы элементов = удаление старого, создание нового

// Было:
<div>Content</div>

// Стало:
<section>Content</section>

// React:
// 1. Удалит <div> и весь его контент
// 2. Создаст новый <section>
// 3. Добавит в него "Content"
// (Это ОЧЕНЬ неэффективно!)

3. Атрибуты и свойства

// Было:
<img src="old.jpg" className="active" />

// Стало:
<img src="new.jpg" className="inactive" />

// React обновит только:
// - src: "old.jpg" -> "new.jpg"
// - className: "active" -> "inactive"

4. Key — самая важная оптимизация

Без ключей React не может определить, какой элемент в списке соответствует какому:

// Плохо (без key):
const items = ['A', 'B', 'C'];
return (
  <ul>
    {items.map((item, index) => <li key={index}>{item}</li>)}
  </ul>
);

// Если добавить элемент, React подумает, что это всё переменилось
// Например, если items = ['X', 'A', 'B', 'C']
// React пересоздаст все <li> элементы!

// Хорошо (с уникальным key):
const items = [
  { id: 1, text: 'A' },
  { id: 2, text: 'B' },
  { id: 3, text: 'C' }
];
return (
  <ul>
    {items.map(item => <li key={item.id}>{item.text}</li>)}
  </ul>
);

// Теперь React знает, какой элемент какой
// При добавлении нового элемента обновит только его

Пример реальной работы

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <p>Description</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

// Старый Virtual DOM:
// div
//   h1 (children: "Count: 0")
//   p (children: "Description")
//   button (onClick handler)

// После клика на кнопку, count = 1, новый Virtual DOM:
// div
//   h1 (children: "Count: 1") <- CHANGED
//   p (children: "Description")
//   button (onClick handler)

// Diff:
// - Только h1.children изменился с "Count: 0" на "Count: 1"
// - p и button остались теми же

// React обновит ТОЛЬКО textContent элемента h1
// Реальный DOM:
// document.querySelector('h1').textContent = 'Count: 1';

Fiber Architecture (как это работает внутри)

На самом деле React использует более сложный алгоритм Fiber, который:

// Fiber — это единица работы в React
const fiber = {
  type: 'h1',
  props: { children: 'Count: 1' },
  key: null,
  parent: divFiber,
  child: null,
  sibling: nextFiber,
  alternate: oldFiber, // старый Fiber для сравнения
  effectTag: 'UPDATE' // 'PLACEMENT', 'UPDATE', 'DELETION'
};

// React проходит по дереву и помечает, что нужно менять
// Потом применяет все изменения в реальный DOM

Практические примеры ошибок

1. Проблема с индексом как key:

// Плохо!
const todos = [
  { id: 1, text: 'Task 1' },
  { id: 2, text: 'Task 2' }
];

todos.map((todo, index) => (
  <li key={index}>{todo.text}</li> // Используем index!
));

// Если удалить первый элемент:
// todos = [{ id: 2, text: 'Task 2' }]

// React будет думать, что это всё тот же элемент (key=0),
// просто с изменённым текстом!
// Если в <li> есть состояние, оно потеряется

2. Условный рендер разных компонентов:

// Плохо:
{isLoggedIn ? <UserProfile /> : <GuestInfo />}

// Когда isLoggedIn меняется, React видит, что первый элемент
// всё ещё <Component>, и переиспользует старый Fiber
// Если в обоих компонентах одинаковые поля, могут быть баги

// Хорошо:
{isLoggedIn ? (
  <UserProfile key="user" />
) : (
  <GuestInfo key="guest" />
)}

// Или разные типы элементов (если возможно):
{isLoggedIn && <UserProfile />}
{!isLoggedIn && <GuestInfo />}

Оптимизация

1. React.memo — для функциональных компонентов

const Button = React.memo(({ label, onClick }) => {
  console.log('Button rendered');
  return <button onClick={onClick}>{label}</button>;
});

// React не будет перендерить Button, если props не изменились

2. useMemo — для дорогих вычислений

const expensiveValue = useMemo(
  () => compute(data),
  [data] // зависимость
);

3. useCallback — для стабильных функций

const handleClick = useCallback(() => {
  console.log('clicked');
}, []);

// Функция не пересоздаётся при каждом рендере

Выводы

  1. Virtual DOM — JavaScript копия реального DOM
  2. Diffing — сравнение старого и нового Virtual DOM
  3. Reconciliation — обновление только измённых элементов в реальном DOM
  4. Key — критично для списков, помогает React определить элементы
  5. Fiber — внутренний алгоритм React для отслеживания изменений
  6. Оптимизация — используй React.memo, useMemo, useCallback для большх деревьев

Понимание этого механизма — ключ к написанию быстрых и эффективных React приложений!

Как React определяет, какие DOM узлы нужно менять? | PrepBro