Главная / Статьи

Разбор различий виртуального DOM: что сравнивает React и почему это имеет значение

Поймите, что на самом деле представляет собой виртуальный DOM в React, как процесс согласования сравнивает две деревья элементов, какие изменения происходят на этапе сохранения состояния и откуда берется преимущество в производительности.

Издатель: REACTAPP.TOP4 мин чтения1032 слов

Источники: How React Virtual DOM Works Under the Hood

Интерфейсы постоянно меняются: появляется уведомление, переключается кнопка, считывается счётчик, перерисовывается список. Каждое такое изменение должно доходить до DOM браузера, и неосторожное обновление DOM — это надёжный способ сделать страницу медленной. Решением React является виртуальный DOM вместе с этапом сравнения, называемым согласованием. В этой статье объясняется, что хранится в памяти React, как он определяет, что изменилось, и почему это помогает, чтобы вы могли правильно понимать процесс обновлений с помощью точной когнитивной модели.

Почему прямое изменение DOM становится дорогостоящим

До появления React обновление интерфейса осуществлялось императивно: находился узел, и он изменялся.

document.getElementById("title").innerText = "Hello";

Это работает нормально на небольшой странице. Но когда сотни или тысячи элементов часто меняются, это становится как медленным, так и сложным в управлении, поскольку изменение DOM может заставить браузер пересчитать:

  • стили и расчёт макета
  • перерисовку окружающего контента
  • перерисовка поврежденных пикселей
  • перезагрузка отдельных частей страницы
  • Каждый раз браузер пересчитывает положение элементов, их размеры и определяет, какие пиксели необходимо перерисовать. Эта работа гораздо затратнее, чем создание или сравнение объектов JavaScript. Кроме того, множество фрагментов кода, воздействующих независимо на DOM, способствуют тому, что экран может выходить из синхронизации со состоянием приложения.

    Что такое виртуальный DOM

    Настоящий DOM

    Настоящий DOM — это динамическое дерево узлов, которое браузер создает на основе маркировки; каждый элемент соответствует одному узлу. Даже это:

    <body>
      <h1>Hello</h1>
    </body>
    

    превращается во внутреннее дерево, связанное с лейаутом и процессом рисования, и частые изменения в нем именно вызывают вышеописанные затратные операции.

    Виртуальное представление

    Виртуальный DOM часто называют легкой копией реального DOM. Точнее говоря, это описание того, как должен выглядеть интерфейс: простые объекты, которые возвращают компоненты при отрисовке. React сначала обновляет это описание, а затем воздействует на браузер. Один элемент выглядит примерно так:

    {
      type: "h1",
      props: {
        children: "Hello"
      }
    }
    

    Такие объекты дешево создавать и сравнивать, поэтому React может определить, что изменилось, прежде чем выполнять какие-либо операции с реальным DOM. JSX — это просто синтаксис для их создания.

    От первой отрисовки до измененной структуры

    При первой отрисовке такого компонента:

    function App() {
      return <h1>Hello World</h1>;
    }
    

    React создает виртуальную структуру, формирует соответствующие узлы DOM и вставляет их на страницу. Теперь обе структуры совпадают, и интерфейс становится видимым.

    Позже обработчик клика может обновить состояние:

    setCount(count + 1);
    

    или родительский компонент передаёт новые свойства. Это сигнализирует о том, что часть интерфейса может потребовать изменений. React не обновляет DOM немедленно; он планирует новую отрисовку, а обновления от одного и того же события обычно группируются вместе.

    Во время этой отрисовки React снова вызывает компонент и получает новую структуру дерева, так что теперь у него есть как предыдущая версия, так и новая. Старая структура дерева:

    <h1>Hello</h1>
    

    Новая структура дерева:

    <h1>Hello Omer</h1>
    

    Задача заключается в том, чтобы точно определить, что изменилось.

    Согласование: как работает сравнение изменений

    Согласование означает сравнение предыдущей виртуальной структуры дерева с новой; само это сравнение обычно называется выявлением различий. Для каждой позиции React проверяет, изменился ли элемент, остался прежним или был добавлен/удалён. Концептуально:

    Old Tree  VS  New Tree
    

    Цель заключается в минимальном наборе изменений, превращающих старый интерфейс в новый. Полностью общий алгоритм сравнения деревьев будет слишком медленным, поэтому React использует две гипотезы. Если меняется тип элемента, например с div на span, React заменяет весь поддерево, а не его дочерние элементы. В списках сравнение дочерних элементов происходит по key, поэтому важны стабильные ключи: они сообщают React, что элемент переместился, а не заменён.

    Когда меняется только текст

    Ранее:

    <h1>Hello</h1>
    

    После:

    <h1>Hello Omer</h1>
    

    <h1> находится в том же месте и имеет тот же тип; отличается только его текст. Поэтому React сохраняет существующий узел DOM и обновляет только узел текста, вместо того чтобы создавать элемент заново. Основа оптимизации — пропуск всех операций, которые не являются абсолютно необходимыми.

    Этап коммита: применение только изменений

    После сравнения React записывает полученные изменения в реальный DOM:

    • обновление текста
    • установка или удаление атрибутов
    • вставка новых элементов
    • удаление элементов, которые исчезли

    Это этап коммита — единственный момент, когда React взаимодействует с DOM, и он изменяет только то, что было отмечено в результате сравнения. Затем браузер перерисовывает затронутые области. Чтобы узнать более подробно о пути от коммита до пикселей, ознакомьтесь с нашим руководством по отрисовке в React от обновлений состояния до пикселей экрана.

    Откуда берется преимущество в производительности

    Положительный эффект достигается за счёт уменьшения количества дорогостоящих операций с DOM. Вместо полной пересборки интерфейса React сравнивает старое и новое описание, вычисляет минимальные различия и влияет только на затронутые узлы. Поскольку сравнение объектов значительно дешевле, чем изменение структуры DOM, это обычно оправдывается в крупных приложениях.

    Два момента помогают сохранять объективность этой модели. Виртуальный DOM не является по своей природе быстрее тщательно отладленного кода DOM; его ценность заключается в возможности писать декларативный код интерфейса при сохранении эффективности обновлений. Кроме того, отрисовка — не бесплатная процедура: React всё равно вызывает ваши компоненты и сравнивает их результаты, поэтому избегание ненужных повторных отрисовок по-прежнему имеет значение.

    Основные выводы

    • Виртуальный DOM — это набор простых объектов, описывающих желаемый интерфейс, который пересоздаётся при каждой отрисовке.
  • Сопоставление различий между старой и новой версиями дерева с использованием типа элемента и key в качестве сокращений.
  • Этап коммитов — единственное место, где React записывает изменения в DOM, причем он сохраняет только различия.
  • Вы жертвуете частью работы по отрисовке и сравнению для того, чтобы избежать большего количества дорогих мутаций DOM и получить более простую модель программирования.