Что такое Virtual DOM?

Spread the love

Недавно я писал о том, что такое DOM и shadow DOM и чем они отличаются друг от друга. Напомним, что Document Object Model (Объектная Модель Документа) – это объектное представление HTML документа и интерфейс для управления этим объектом. Shadow DOM можно рассматривать как «облегченную» версию DOM. Это также объектно-ориентированное представление элементов HTML, но Shadow DOM позволяет нам разделить основной DOM на меньшие изолированные части, которые можно использовать в документах HTML.

Другой похожий термин, с которым вы, возможно, сталкивались, это «Virtual DOM». Хотя эта концепция существует уже несколько лет, она стала более популярной благодаря использованию ее в различных фреймворках, таких как React, Vuejs и т.д.. В этой статье я расскажу, что такое виртуальный DOM, чем он отличается от обычного DOM и как он используется.

Зачем нам нужен virtual DOM?

Чтобы понять, почему возникла концепция виртуального DOM, давайте вернемся к DOM. Как я уже упоминал, в DOM есть две части – объектное представление документа HTML и API для управления этим объектом.

Например, давайте возьмем в качестве примера простой HTML-документ с неупорядоченным списком и одним элементом списка.

<!doctype html>
<html lang="en">
 <head></head>
 <body>
    <ul class="list">
        <li class="list__item">List item</li>
    </ul>
  </body>
</html>

Этот документ может быть представлен как следующее DOM дерево:

html
  head lang="en"
    body
      ul class="list"
        li class="list__item"
          "List item"

Допустим, мы хотим изменить содержимое первого элемента списка на “List item one”, а также добавить второй элемент списка. Для этого нам нужно будет использовать API DOM, чтобы найти элементы, которые мы хотим обновить, создать новые элементы, добавить атрибуты и контент, а затем, наконец, обновить сами элементы DOM.

const listItemOne = document.getElementsByClassName("list__item")[0];
listItemOne.textContent = "List item one";

const list = document.getElementsByClassName("list")[0];
const listItemTwo = document.createElement("li");
listItemTwo.classList.add("list__item");
listItemTwo.textContent = "List item two";
list.appendChild(listItemTwo);

DOM не был сделан для этого …

Когда в 1998 году была выпущена первая спецификация для DOM, мы создавали и управляли веб-страницами по-другому. API DOM использовался для создания и обновления содержимого страниц гораздо реже, чем это делается сегодня.

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

Более того, из-за способа настройки API-интерфейсов обычно проще выполнять более дорогостоящие операции, когда мы обновляем большие части документа, чем находить и обновлять конкретные элементы. Возвращаясь к нашему примеру со списком, в некотором смысле проще заменить весь неупорядоченный список новым, чем модифицировать определенные элементы.

const list = document.getElementsByClassName("list")[0];
list.innerHTML = `
<li class="list__item">List item one</li>
<li class="list__item">List item two</li>
`;

В этом конкретном примере разница в производительности между методами, вероятно, незначительна. Однако по мере роста размера веб-страницы становится все более важным выбирать и обновлять только то, что необходимо.

… но причем здесь виртуальный DOM!

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

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

Как выглядит виртуальный DOM?

Слово виртуальный имеет тенденцию добавлять определеную загадочность там где ее на самом деле нет. Фактически, виртуальный DOM – это просто обычный объект Javascript.

Давайте вернемся к дереву DOM, которое мы создали ранее:

html
  head lang="en"
    body
      ul class="list"
        li class="list__item"
          "List item"

Дерево может быть представлено как объект Javascript.

const vdom = {
    tagName: "html",
    children: [
        { tagName: "head" },
        {
            tagName: "body",
            children: [
                {
                    tagName: "ul",
                    attributes: { "class": "list" },
                    children: [
                        {
                            tagName: "li",
                            attributes: { "class": "list__item" },
                            textContent: "List item"
                        } // end li
                    ]
                } // end ul
            ]
        } // end body
    ]
} // end html

Мы можем думать об этом объекте как о нашем виртуальном DOM. Как и исходный DOM, это объектное представление нашего HTML-документа. Но так как это простой объект Javascript, мы можем свободно и часто манипулировать им, не касаясь реального DOM, пока нам это не понадобится.

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

const list = {
    tagName: "ul",
    attributes: { "class": "list" },
    children: [
        {
            tagName: "li",
            attributes: { "class": "list__item" },
            textContent: "List item"
        }
    ]
};

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

Теперь, когда мы увидели, как выглядит виртуальный DOM, как он работает для решения проблем производительности и удобства использования DOM?

Как я уже упоминал, мы можем использовать виртуальный DOM, чтобы выделить конкретные изменения, которые необходимо внести в DOM, и сделать эти конкретные обновления по отдельности. Давайте вернемся к нашему неупорядоченному списку и внесем те же изменения, что и в DOM API.

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

const copy = {
    tagName: "ul",
    attributes: { "class": "list" },
    children: [
        {
            tagName: "li",
            attributes: { "class": "list__item" },
            textContent: "List item one"
        },
        {
            tagName: "li",
            attributes: { "class": "list__item" },
            textContent: "List item two"
        }
    ]
};

Эта копия используется для создания того, что называется «diff» между исходным и виртуальным DOM, в данном случае исходным списком, и обновленным списком. Diff может выглядеть примерно так:

const diffs = [
    {
        newNode: { /* new version of list item one */ },
        oldNode: { /* original version of list item one */ },
        index: /* index of element in parent's list of child nodes */
    },
    {
        newNode: { /* list item two */ },
        index: { /* */ }
    }
]

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

Например, мы могли бы перебрать каждый diff и добавить нового потомка или обновить старого в зависимости от того, что указано в diff.

const domElement = document.getElementsByClassName("list")[0];

diffs.forEach((diff) => {

    const newElement = document.createElement(diff.newNode.tagName);
    /* Add attributes ... */
    
    if (diff.oldNode) {
        // If there is an old version, replace it with the new version
        domElement.replaceChild(diff.newNode, diff.index);
    } else {
        // If no old version exists, create a new node
        domElement.appendChild(diff.newNode);
    }
})

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

Виртуальный DOM и фреймворки

В реальности с виртуальным DOM чаще работают через фреймворки, а не взаимодействуют с ним напрямую, как я показал в примере выше.

Фреймворки, такие как React и Vue, используют концепцию виртуального DOM для более производительных обновлений DOM. Например, наш компонент списка может быть написан в React следующим образом.

import React from 'react';
import ReactDOM from 'react-dom';

const list = React.createElement("ul", { className: "list" },
    React.createElement("li", { className: "list__item" }, "List item")
);

ReactDOM.render(list, document.body);

Если бы мы хотели обновить наш список, мы могли бы просто переписать весь шаблон списка и снова вызвать ReactDOM.render() , передав новый список.

const newList = React.createElement("ul", { className: "list" },
    React.createElement("li", { className: "list__item" }, "List item one"),
    React.createElement("li", { className: "list__item" }, "List item two");
);

setTimeout(() => ReactDOM.render(newList, document.body), 5000);

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

DOM против virtual DOM

Напомним, что виртуальный DOM – это инструмент, который позволяет нам взаимодействовать с элементами DOM более простым и производительным способом. Это Javascript-объектное представление DOM, которое мы можем изменять так часто, как нам нужно. Изменения, внесенные в этот объект, затем сопоставляются, а изменения в реальном DOM производятся намного реже.

Оригинал


Spread the love

Добавить комментарий

Ваш e-mail не будет опубликован.