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

Как Virtual DOM оптимизирует рендеринг?

1.8 Middle🔥 251 комментариев
#Браузер и сетевые технологии

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

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

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

Virtual DOM: Оптимизация рендеринга в React и других фреймворках

Virtual DOM (VDOM) - это абстрактное представление реального DOM в памяти. Это ключевой механизм, который позволяет React эффективно обновлять интерфейс, минимизируя дорогостоящие манипуляции с реальным DOM.

Почему Virtual DOM нужен

Проблема: DOM манипуляции очень дорогие

// Каждая манипуляция с DOM запускает перерасчет стилей и рендеринг
const element = document.getElementById('app');

element.innerHTML = '<p>Item 1</p>'; // Перерисовка 1
element.innerHTML += '<p>Item 2</p>'; // Перерисовка 2
element.innerHTML += '<p>Item 3</p>'; // Перерисовка 3

// 3 переренды! Это медленно.

Проблема: Не всегда ясно, что изменилось

// Как узнать, изменился ли конкретный элемент?
const newState = {
  users: [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Carol' }
  ]
};

// Какие элементы перерисовать? Все? Только новые?
// Браузер этого не знает - нужна абстракция

Как Virtual DOM работает

Шаг 1: React создает VDOM дерево в памяти

const userList = (
  <ul>
    <li>Alice</li>
    <li>Bob</li>
  </ul>
);

// React внутренне представляет это как:
// {
//   type: 'ul',
//   props: { children: [...] },
//   children: [
//     { type: 'li', props: {}, children: ['Alice'] },
//     { type: 'li', props: {}, children: ['Bob'] }
//   ]
// }

Шаг 2: При изменении состояния React создает новое VDOM дерево

const [users, setUsers] = useState(['Alice', 'Bob']);

const handleAddUser = () => {
  // Создаётся НОВОЕ VDOM дерево
  setUsers([...users, 'Carol']);
};

// React НЕ обновляет DOM сразу
// Вместо этого создаёт новое дерево в памяти

Шаг 3: React сравнивает старое и новое VDOM (Reconciliation)

Олд VDOM:              Новый VDOM:
<ul>                   <ul>
  <li>Alice</li>  ===>   <li>Alice</li>  ✓ одинаково
  <li>Bob</li>           <li>Bob</li>    ✓ одинаково
                         <li>Carol</li>  <- НОВЫЙ элемент
</ul>                  </ul>

Шаг 4: React обновляет ТОЛЬКО то, что изменилось

// Вместо:
ul.innerHTML = '<li>Alice</li><li>Bob</li><li>Carol</li>';

// React делает только:
ul.appendChild(document.createElement('li'));
// И устанавливает текст в новый элемент

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

Правило 1: Сравнение типов элементов

// Разные типы = полностью пересоздать
<div>Hello</div>  ->  <span>Hello</span>
// React удалит div и создаст span (даже если содержимое одинаково)

// Одинаковые типы = сравнить props
<div className="old">Hello</div>  ->  <div className="new">Hello</div>
// React обновит только className

Правило 2: Ключи (Keys)

// ПЛОХО - без ключей
const list = users.map(user => <li>{user.name}</li>);
// Если переупорядочить список, React переиспользует элементы неправильно

// ХОРОШО - с ключами
const list = users.map(user => <li key={user.id}>{user.name}</li>);
// React знает, какой элемент соответствует какому пользователю

Пример проблемы без ключей:

const [items, setItems] = useState(['Apple', 'Banana']);
const [input, setInput] = useState('');

return (
  <>
    <input value={input} onChange={(e) => setInput(e.target.value)} />
    <ul>
      {items.map((item, index) => (
        <li key={index}> {/* ПЛОХО! Используем index */}
          <input defaultValue={item} />
        </li>
      ))}
    </ul>
  </>
);

// Если добавить новый item в начало:
// React не знает, что это новый элемент
// Он будет переиспользовать старые input элементы
// Результат: значения input перепутаются

Правильно с уникальными ключами:

{items.map((item) => (
  <li key={item.id}> {/* Уникальный ID, не index */}
    <input defaultValue={item.name} />
  </li>
))}

Процесс обновления Virtual DOM

1. Изменение состояния (setState, setCount, ...)
   |
   v
2. React создает новый VDOM
   |
   v
3. React сравнивает старый и новый VDOM
   |
   v
4. React вычисляет минимум изменений (patch)
   |
   v
5. React применяет patch к реальному DOM
   |
   v
6. Браузер перерисовывает только изменившиеся элементы

Практический пример: React Component

function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}> {/* Критичный ключ */}
          <span>{user.name}</span>
          <span>{user.email}</span>
        </li>
      ))}
    </ul>
  );
}

// Если users изменился:
const oldVDOM = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' }
];

const newVDOM = [
  { id: 1, name: 'Alice', email: 'alice@example.com' }, // одинаково
  { id: 3, name: 'Carol', email: 'carol@example.com' }  // новый
];

// React обновляет:
// 1. Удаляет li с id=2 (Bob)
// 2. Добавляет новый li с id=3 (Carol)
// 3. li с id=1 остаётся без изменений

Оптимизация Virtual DOM: React.memo

// ПЛОХО - перерисовывается каждый раз
function UserItem({ user }) {
  console.log('Rendering:', user.name); // Выведет каждый раз
  return <li>{user.name}</li>;
}

// ХОРОШО - перерисовывается только если user изменился
const UserItem = React.memo(({ user }) => {
  console.log('Rendering:', user.name); // Выведет только при изменении
  return <li>{user.name}</li>;
});

// Результат: если user.name одинаков, компонент не перерисуется

Оптимизация: useMemo

function ComponentWithExpensiveCalc({ data }) {
  // ПЛОХО - пересчитывается каждый рендер
  const expensive = data.map(x => x * 2).reduce((a, b) => a + b);

  return <div>{expensive}</div>;
}

// ХОРОШО - пересчитывается только если data изменилась
function ComponentWithExpensiveCalc({ data }) {
  const expensive = useMemo(() => {
    return data.map(x => x * 2).reduce((a, b) => a + b);
  }, [data]);

  return <div>{expensive}</div>;
}

Батчинг обновлений

// React 18+ автоматически батчит обновления
function handleClick() {
  setCount(c => c + 1); // Не обновляет UI сразу
  setName('Alice');      // Не обновляет UI сразу
  // React сделает один рендер для обоих изменений
}

// Старое поведение (React 17):
// 2 рендера

// Новое поведение (React 18+):
// 1 рендер

Сравнение с альтернативами

Без Virtual DOM (jQuery стиль):

// Нужно вручную обновлять DOM
$('#users').html('');
users.forEach(user => {
  $('#users').append(`<li>${user.name}</li>`);
});
// Каждый append = перерисовка

С Virtual DOM (React):

function UserList({ users }) {
  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}
// React оптимизирует автоматически

Ограничения Virtual DOM

// 1. Для очень больших списков Virtual DOM может быть медленнее
// Решение: Виртуализация списка (react-window)

// 2. Virtual DOM добавляет overhead памяти
// Решение: Используй Key правильно

// 3. Может быть медленнее для очень простых компонентов
// Решение: Используй Native DOM для микроскопических операций (редко)

Итог

Virtual DOM оптимизирует рендеринг через:

  1. Абстракция - VDOM в памяти вместо прямой работы с DOM
  2. Батчинг - накапливание изменений и одна перерисовка
  3. Диффинг - сравнение старого и нового дерева
  4. Патчинг - обновление только измененных элементов
  5. Ключи - отслеживание элементов при переупорядочивании

Это позволяет разработчикам писать код так, как будто весь компонент перерисовывается, но на самом деле React обновляет только необходимое. Это одна из главных причин популярности React.