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

Возможно, я ошибаюсь. Поэтому, если вам не сложно, вы можете поправить меня в комментариях.

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

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

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

const obj1 = {
  value: "Text",
};

const el = document.createElement("div");

const updateView = (value) => {
  el.textContent = value;
};

updateView(obj1.value);

const proxy = new Proxy(obj1, {
  set: (target, prop, value) => {
    if (prop === "value") {
      updateView(value);
      target[prop] = value;
    }
    return true;
  },
});

console.log(el.innerHTML);

proxy.value = "Text 1";

console.log(el.innerHTML);

Вы можете реализовать это действие без Proxy, но в целом этот пример показывает, как все это примерно работает. Вы можете добавить сравнение со старым значением (oldValue !== value), но тогда вы можете начать искать и внедрять тему глубокого сравнения переменных и тому подобное, и тогда пример будет намного сложнее.

В общем, главная идея заключается в том, что мы изменили значение объекта, оболочка (Proxy) перехватила событие и вызвала функцию, которую мы описали.

Теперь давайте представим, что мы создаем веб-сайт. У сайта есть собственное DOM-дерево элементов.

Пример DOM дерева
Пример DOM дерева

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

const a = {
 tag:"DIV",
 childNodes:[...],
 listeners:[...],
 atrributes:[...]
}

Нюанс в том, что это может быть медленно и вообще-то бессмысленно. По сути, что подразумевается под “обновлением представления”? Если перечислять действия, которые выполняются, то по сути это замена одного узла другим, его удаление и добавление. Обновление текста узла и обновление атрибутов узла, а также работа обработчиков событий с новыми данными и, собственно, это всё. Что еще нужно для обновления DOM на веб-сайте? Если вся работа будет проходить через фреймворк и никакие сторонние библиотеки внезапно не добавят свои узлы в DOM, то, по сути, этого будет достаточно.

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

Ну, на самом деле возможно. И уже давно, например, 4 или 5 лет назад, уже существует куча фреймворков или библиотек, которые обходятся “без” работы с виртуальным DOM.

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

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

Короче говоря, представьте себе такую ситуацию. У вас есть строка содержащая HTML-код, и вам нужно создать 1000 DOM-узлов по её шаблону.

const stringHTML = "<div>123</div>";

Самый простой способ сделать это - создать из строки один DOM узел и непрерывно клонировать его. То есть, точно так же, как с class в javascript, когда вы создаете 1000 новых экземпляров класса.

Примерно так это будет выглядеть в коде:

const doc = new DOMParser().parseFromString(stringHTML, 'text/html');
const el = doc.getElementsByTagName("div")[0];
const newEl = document.createElement("div");
for(let i = 0; i < 1000; i++) newEl.appendChild(el.cloneNode(true));

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

for(let el of document.querySelectorAll("*")){...}

То есть, подводя итог, основной посыл заключается в том, что виртуальный DOM - вещь необходимая, но не всегда нужная в реактивности. Сегодня textContent и setAttribute могут работать довольно хорошо без обновления сначала виртуального DOM, а затем реального.

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

В целом, мы также можем упомянуть о существовании "технологии keyed", когда задаваемый ключ из данных (и не только) привязан к узлам, но в этом смысле разница в реактивности вообще не связана, потому что это уже другая концепция.

Сегодня, для обновления по минимуму DOM, достаточно всего лишь объекта со следующими свойствами:

1. Ссылка на DOM узел
2. Ссылки на те дочерние узлы, которые содержатся в узле
3. Старые значения обновляемых данных в DOM
4. Ключ (опционально)

И, в принципе этого достаточно. Не нужен тег, какие-то объекты лишние и т.д. Понятное дело, что это всё поверхностно, но, в целом, это как-то так.

Всем большое спасибо за прочтение статьи!

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


  1. ednersky
    28.03.2024 20:30
    +3

    вся эта жесть (рядом стоящее DOM-дерево) всегда вызывало у меня недоумение

    сперва они нарисовали dom

    потом они стали компоненты описывать своим языком разметки (собственные теги)

    и получили html over dom over html

    когда-то в Фидо существовал мем: фидошники долго пытались использовать видеомагнитофоны для хранения комп данных

    так вот однажды чувак опубликовал пост: "у меня наконец получилось записать туда видеофайл!"

    на что ему ответили: "бро, ты только что научился смотреть видео с видеокассеты!"

    вот так и эта реактивность


    1. gmtd
      28.03.2024 20:30
      +6

      Не совсем

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

      Видеомагнитофоны часто использовали для архива
      На одну кассету VHS помещалось около 4Гб данных - огромадный для того времени объем, соответственно, дешевизна хранения. Это эпоха сидиромов и 760Мб дисков - недешевых и часто портящих данные

      Так что смеяться над тем "чуваком" как минимум глупо.


      1. Sabirman
        28.03.2024 20:30

        Еще один интересный круг: в начале нулевых все браузеры поддерживали микрософтовский VBScript. Он был сложнее JavaScript, потому что там была типизация (не строгая, но была), поэтому от него отказались. Сейчас все переходят на TypeScript, именно из-за наличия в нем типизации. Причём язык, опять-таки, разрабатывается MS-ом.


        1. Sabirman
          28.03.2024 20:30

          .. и, кстати, VBScript изначально "из коробки" работал на сервере :)


        1. gmtd
          28.03.2024 20:30

          Насколько помню [VisualBasic]Script не поддерживался в Netscape Navigator

          В любом случае, политика Микрософт тогда была такая агрессивная и нечестная, что ее технологии вряд ли бы согласились сделать стандартом.


          1. Sabirman
            28.03.2024 20:30

            Netscape Navigator - это конец 90-х. К 2000-м он уже сдулся и, по большому счету, остался только IE.


            1. gmtd
              28.03.2024 20:30

              К 2000-му JavaScript уже был стандартом в вебе, его не могли просто так взять и заменить


        1. WebEagle
          28.03.2024 20:30

          Не помню на счет нулевых, но году в 2010 VBScript удалось использовать только в IE, в остальных работать отказывался. Но на нем ещё тогда можно было с файловой системой взаимодействовать. Уязвимостей много было, да и в целом на JS по-приятнее было писать.


    1. Sabirman
      28.03.2024 20:30

      Еще аналогия - серверный рендеринг. Сначала веб-сервера выдавали html-ку, потом стали рендерить веб страницу на клиенте с помощью JS. Теперь запускают этот JS на сервере, рендерят им html-ку и опять отдают клиенту готовую веб-страницу.


      1. tonx92
        28.03.2024 20:30

        Этот костыль связан с импотенцией поисковых систем, думаю у них это пройдет.


        1. gmtd
          28.03.2024 20:30

          Это не импотенция поисковых систем - SEO вполне достигается другими неизвращенными способами типа пререндеринга или отделного статичного контента для поисковиков. Да и умеет Google в SPA уже

          Это попытка фронтов захвтатить сервер


          1. nin-jin
            28.03.2024 20:30
            +1

            В SPA может и умеет, а вот в вебсокеты - нет.


          1. strokoff
            28.03.2024 20:30

            Google не умеет в SPA - точнее любой классический сайт сверстанный семантично и отданный с помощью SSR будет выше в поисковой выдаче чем SPA. Так что SSR или SSG незаменимы если речь идёт о поисковом продвижении. Причем если вы клиенту и боту будете отдавать разные страницы, можно ещё и банан получить за попытки делать дорвеи


            1. gmtd
              28.03.2024 20:30
              +1

              точнее любой классический сайт сверстанный семантично и отданный с помощью SSR будет выше в поисковой выдаче чем SPA

              Откуда это утверждение?
              Ссылки на пруфы, плз, про преимущество [гидратированного?] SSR


              1. strokoff
                28.03.2024 20:30

                Сходите от обратного, найдите spa который в выдаче будет выше ssr. В моем случае 99.9% сайтов на любой ваш запрос в топ 3 будет ssr/ssg. Не вижу смысла доказывать очевидные вещи.

                P.s. ещё есть Яндекс в РФ и его игнорировать нельзя.


                1. gmtd
                  28.03.2024 20:30

                  Не буду спорить с вашей логикой
                  Она прекрасна


                  1. strokoff
                    28.03.2024 20:30

                    Я бы и сам с удовольствием рассмотрел SEO успешный проект в конкурентной нише реализованный с помощью SPA без доп. Махинаций на стороне сервера. Даже с учётом игнорирования Яндекса. Если найдется пример, обязательно напишите.



  1. alexnozer
    28.03.2024 20:30
    +5

    Раньше VDOM подавался как крутая фича фреймворка. Говорили "React крут, потому что там VDOM и это позволяет оптимизировать отрисовку и уменьшить количество медленных DOM-операций, потому что сравнивать объекты дешевле".

    А последнее время стали говорить иначе. DOM-операции на самом деле не такие уж и медленные, как оказалось. Браузеры делают кучу оптимизаций на уровне движка. Но DOM-операции нужно правильно готовить (кеширование, батчинг, отложенные обновления и тд).

    VDOM уже начал всем мешать и сейчас наоборот, фишкой фреймворка является отсутствие VDOM. И новые фреймворки идут туда же. Да и старые тоже (Vapor Vue, Angular Incremental DOM). Тот же Lit не использует VDOM и по сухому бенчмарку выигрывает троицу. Так что да, VDOM как будто потихоньку отходит как концепция.


    1. tonx92
      28.03.2024 20:30
      +1

      VDOM Изначально бесполезная фича, которую популяризировала секта реакта. Просто реальных конкурентных преимуществ перед полноценными фреймворками у реакта нет, был придуман миф мол он быстрее. А на деле производительности что бы крутить сайты много не надо. Проблемы только в огромных списках, но и для них есть virtual scroll. Сама концепция VDOM изначально не очень, зачем нужен виртуальный DOM когда просто можно не засерать обычный, и вообще откуда в нем столько изменений вдруг взялось что прям не успевает браузер рендерить картинки с текстом.


      1. alexnozer
        28.03.2024 20:30

        VDOM Изначально бесполезная фича

        Не то чтобы совсем бесполезная. Когда он только появился, он как-будто решал свою задачу. DOM действительно мог быть медленным на больших объёмах данных или при слишком частых обновлениях.

        Но с тех пор выросли аппаратные мощности устройств и браузеры постоянно совершенствуют механизмы работы DOM и оптимизирующие движки для JS. А некоторые фреймворки показали, что точечная работа с DOM при грамотном подходе работает быстрее VDOM.

        А на деле производительности что бы крутить сайты много не надо

        Обычные сайты и не надо на фреймворках делать. Генерировать страницу сервером и отдать в браузер, или заранее нагенерировать статических файлов и раздавать их с CDN. А некоторым сайтам и вовсе JS не нужен, не говоря уже о том, чтобы тащить рантайм фреймворка и вебпака и рисовать всё на клиенте. Но конечно от сайта зависит.

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

        А оно появилось, когда перестали рендерить картинки с текстом, а стали рендерить дата-гриды на сотни строк с сортировкой, фильтрацией и реалтайм-обновлением стока акций раз в 500мс, или когда начали рендерить онлайн-редактор документов с совместным доступом на редактирование.


  1. 19Zb84
    28.03.2024 20:30
    +1

    Нет плохих и хороших технологий. Их можно правильно использовать, а можно не правильно.

    Буквально несколько дней назад сделал проект, в котором вместе используются web components и react.

    У react есть свои плюсы (хотя не люблю использовать как библиотеку на которой весь сайт делаю) и у web components свои (это основнаятехнология которую я использую)


  1. gmtd
    28.03.2024 20:30
    +1

    Не могу найти, но видел сравнение размера бандлов svelte (или solid.js) (не vdom) и vue ( vdom )

    На "hello world" - 5Кб и 60Кб, при росте размера прикладного кода через некоторое время бандл svelte начинает сильно превышать бандл vue. Соответственно, скорость тоже возможно падает

    Так что размер приложения очень важен для оценок. Небольшое можно и на document.querySelector самому написать, а для большого vdom - сильное спасение


    1. ILaeeeee
      28.03.2024 20:30
      +1

      На "hello world" - 5Кб и 60Кб, при росте размера прикладного кода через некоторое время бандл svelte начинает сильно превышать бандл vue. Соответственно, скорость тоже возможно падает

      Скорость вряд ли. А по размеру вполне очевидная закономерность.

      На небольшом проекте генератор будет выигрывать комбайн. Т.к. количество генериуемого кода будет меньше кода комбайна.

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


  1. Zoolander
    28.03.2024 20:30
    +1

    Есть SolidJS и SvelteJS, вероятно, вы сможете подрезать оттуда пару идей для развития своего Фреймворка.

    SolidJS интересен синтаксисом JSX, который максимально напоминает React, для этого используется кастомный JSX (если кто не в курсе, то JSX это просто синтаксис записи объектов в XMLподобном виде, Babel превращает все эти теги и атрибуты в объекты и их поля)


    1. antonmak1 Автор
      28.03.2024 20:30

      Здравствуйте! Синтаксис jsx не используется в фреймворке по причине достижения максимально возможной скорости работы. Если бы он использовался - он, конечно, очень удобен, но, сам по себе загоняет в определённые рамки + "конкуренция" с jsx фреймворками просто огромная. Я предполагаю, что есть перспектива сделать свой template с расширением конкретным для удобной работы, но, я пока не думаю, что это сейчас необходимо.


      1. stvoid
        28.03.2024 20:30
        +2

        Для чего вот свой template изобретать? По моему эта штука должна решать проблему того, что вам необходимо применять например свои специальные атрибуты или как-то обернуть многословность там, где она есть.

        В свое время выбрал vue, вместо react просто потому что ломать голову этим jsx было совсем неприятно, а раздел шаблона в компонентах vue сильно напоминал какой-нибудь jinja


        1. antonmak1 Автор
          28.03.2024 20:30

          Изобретать свой велосипед без надобности не нужно - это я понимаю. Сейчас, я не думаю, что стоит делать шаги в данном направлении по причинам, описанным выше в комментариях. Добавлю только, что в целом я считаю, что попробовать сделать template можно только лишь для удобства. Чтобы вместо html подсвечивался бы синтаксис чуть другой. Но, это действительно много работы и, боюсь, в одиночку я уже такое не потяну. Всё, что сейчас в фреймворке над html'ем - это минимальная необходимость для современной работы (да и то пока не хватает многого). Но, зато хоть всё работает реально очень быстро благодаря этому)