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

Как в React происходит согласование деревьев?

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

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

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

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

Как в React происходит согласование деревьев (Reconciliation)

Это один из самых важных механизмов React. Когда состояние меняется, React должен эффективно обновить DOM. Давай разберемся в деталях.

Что такое согласование

Reconciliation - процесс сравнения старого Virtual DOM с новым Virtual DOM и обновления реального DOM только там, где что-то изменилось.

// Компонент меняет состояние
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

// Когда пользователь кликает:
// 1. setCount(1) -> trigger re-render
// 2. React создает новое Virtual DOM дерево
// 3. Сравнивает со старым деревом
// 4. Обновляет только <p>Count: 1</p> в реальном DOM

Алгоритм Diffing

React использует эвристический алгоритм O(n) вместо O(n^3):

Эвристика 1: Different element types

// Если тип элемента изменился, React пересоздает дерево целиком

// Было
<div>
  <Counter />
</div>

// Стало
<span>
  <Counter />
</span>

// React скажет: "<div> стал <span>, нужно пересоздать все"
// Старый Counter уничтожен, новый создан заново

Эвристика 2: Attributes

// Было
<div className="red">Hello</div>

// Стало
<div className="blue">Hello</div>

// React обновит только className, остальное не трогает

Эвристика 3: Children with Keys

// БЕЗ key (плохо)
<ul>
  {items.map(item => <li>{item.name}</li>)}
</ul>

// Если добавить item в начало - React пересоздаст ВСЕ li!
// Потому что не знает какой li какому item соответствует

// С key (хорошо)
<ul>
  {items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>

// Теперь React знает: li с key=3 это всегда item с id=3
// Добавляет новый item -> добавляет новый li, старые не трогает

Жизненный цикл Reconciliation

Фаза 1: Render Phase (можно прерваться)

// React вызывает функцию компонента
function MyComponent() {
  return <div>...</div>; // <- это вызов
}

// Результат - новое Virtual DOM дерево
// На этой фазе нет побочных эффектов (useEffect еще не запущен)
// Эта фаза может быть запущена несколько раз

Фаза 2: Commit Phase (гарантированное выполнение)

// React применяет изменения к реальному DOM
// 1. Обновляет DOM элементы
// 2. Запускает useLayoutEffect (синхронно)
// 3. Браузер перерисовывает (paint)
// 4. Запускает useEffect (асинхронно)

Virtual DOM и реальный DOM

// Virtual DOM - это просто объект JavaScript
const vdom = {
  type: 'div',
  props: {
    className: 'container',
    children: [
      {
        type: 'h1',
        props: { children: 'Title' }
      },
      {
        type: 'p',
        props: { children: 'Content' }
      }
    ]
  }
};

// React преобразует это в реальный DOM:
<div className="container">
  <h1>Title</h1>
  <p>Content</p>
</div>

Optimization: Memoization

Если пересчет компонента дорогой, используй React.memo:

// Компонент будет пересчитан, даже если props не изменились
function BigList({ items }) {
  return (
    <ul>
      {items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

// Обернуть в memo
const BigListMemo = React.memo(BigList);
// Теперь если items массив the same (по ссылке) - компонент не пересчитывается

// Но ВНИМАНИЕ! Нужно убедиться что props стабильны
function Parent() {
  const items = [1, 2, 3]; // создается каждый раз!
  return <BigListMemo items={items} />; // бесполезно, items всегда "новый"
}

// Правильно
function Parent() {
  const items = useMemo(() => [1, 2, 3], []); // один раз
  return <BigListMemo items={items} />;
}

Батчинг (Batching)

React группирует несколько setState в один re-render:

function handleClick() {
  setCount(count + 1);     // <- не перерендер
  setTitle('Clicked');     // <- не перерендер
  setActive(true);         // <- не перерендер
}                          // <- только ОДИН перерендер здесь

// В React 18+ это работает везде (раньше только в обработчиках)
setTimeout(() => {
  setCount(count + 1);     // <- батчирован
  setTitle('Later');       // <- батчирован
}, 1000);                  // <- один перерендер

Когда перерендер неизбежен

// Компонент перерендерится если:
// 1. Его props изменились
function Card({ title }) {
  return <h1>{title}</h1>; // перерендер если title изменился
}

// 2. Его state изменился
function Counter() {
  const [count, setCount] = useState(0); // перерендер если count изменился
  return <div>{count}</div>;
}

// 3. Контекст изменился
function Profile() {
  const user = useContext(UserContext); // перерендер если user изменился
  return <div>{user.name}</div>;
}

// 4. Родитель перерендерился (если не используешь memo)
function Child() {
  return <div>Child</div>; // перерендер вместе с родителем
}

Ключевые insights

  1. Virtual DOM это не "быстрее" чем реальный DOM

    • Это просто инструмент для эффективного обновления
  2. Keys критичны для списков

    • Без keys React может обновить не те элементы
    • Используй стабильный ID, не индекс
  3. Reconciliation это дорого

    • Минимизируй перерендеры с помощью memo, useMemo
    • Но не переусложняй код ради оптимизации
  4. Render и Commit фазы разные

    • Render может быть прерван
    • Commit всегда завершается

Профилирование

// DevTools Profiler показывает:
// - Какие компоненты перерендерились
// - Сколько времени потребовалось
// - Почему произошел перерендер

// Используй для поиска узких мест

Вывод

Reconciliation - это оптимизированный процесс обновления DOM. React:

  1. Сравнивает старое и новое Virtual DOM деревья
  2. Определяет минимальные изменения
  3. Обновляет реальный DOM
  4. Запускает побочные эффекты

Понимание этого процесса критично для написания производительного React кода.