Why is reactivity without VDOM (with real DOM) better than reactivity with VDOM?

Hi all! In this article, I'd like to share my thoughts on why a virtual DOM can be avoided when building reactivity today. I've been working with all this for about a year and a half now, building the Cample.js framework, and I have some thoughts on it.

Maybe I'm wrong. Therefore, if it’s not difficult for you, you can correct me in the comments.

Since I start talking about the differences, I would like to write about what “reactivity” actually is.

Reactivity is when a view is bound to data in such a way that a change in the data causes a change in the view itself.

A simple example of reactivity can be implemented using Proxywhen we write some information to an object and, depending on this, the function that we described will be called.

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);

You can implement this action without Proxy, but in general this example shows how it all roughly works. You can add a comparison with the old value (oldValue !== value), but then you can start looking and implementing the topic of deep variable comparisons and the like, and then the example will be much more complex.

In general, the main idea is that we have changed the value of the object, the shell (Proxy) intercepted the event and called the function we described.

Now let's imagine that we are creating a website. The site has its own DOM tree of elements.

Example DOM tree

Example DOM tree

And for this, in javascript we can create an object in which the real DOM will be reflected through nesting. When data changes, we can first update this object and then update the DOM itself, but there is a caveat.

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

The caveat is that it can be slow and generally pointless. Basically, what is meant by “updating the view”? If we list the actions that are performed, then in essence it is the replacement of one node with another, its removal and addition. Updating the node text and updating the node attributes, as well as the work of event handlers with new data and, in fact, that’s all. What else is needed to update the DOM on a website? If all the work goes through the framework and no third-party libraries suddenly add their nodes to the DOM, then, in fact, this will be enough.

You don't need to work with the virtual DOM. Why do you need to update it first and then suddenly update the real DOM under these conditions? Isn't it possible to immediately update the real DOM just by comparing the data?

Well, actually it's possible. And for a long time, for example, 4 or 5 years ago, there have already been a bunch of frameworks or libraries that do “without” working with the virtual DOM.

No, that is, they do not work with it in the established sense, when tags and other things are compared – no. The point is different. Now there is one pattern that is clear to almost everyone – creating a component template.

So, this is where the virtual DOM is used. And he is the coolest there, because he helps to find the fastest way to assemble DOM nodes.

In short, imagine this situation. You have a string containing HTML code, and you need to create 1000 DOM nodes using its template.

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

The easiest way to do this is to create one DOM node from a string and clone it continuously. That is, exactly the same as with class in javascript when you create 1000 new instances of a class.

This is what it will look like in code:

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));

This is where the virtual DOM comes in handy to create the best possible template! You, of course, can do it this way, but it will be terribly slow, because the algorithm for creating links to DOM nodes will work as a simple search until the desired one:

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

That is, to summarize, the main message is that the virtual DOM is a necessary thing, but not always necessary in reactivity. Today textContent And setAttribute can work quite well without updating first the virtual DOM and then the real one.

Yes, this happens with the virtual DOM very fast reactivitybut the fact that you update the object first and then the DOM isn't always cool, because it's another step in the code that isn't always necessary unless you're going through nodes that were added somewhere outside the framework or libraries.

In general, we can also mention the existence of “keyed technology”, when a given key from data (and not only) is tied to nodes, but in this sense the difference in reactivity is not related at all, because this is a different concept.

Today, to update the DOM as little as possible, all you need is an object with the following properties:

1. Link to DOM node
2. Links to those child nodes contained in the node
3. Old values ​​of updated data in DOM
4. Key

And, in principle, this is enough. A tag is not needed, some objects are unnecessary, etc. It’s clear that this is all superficial, but, in general, it’s something like that.

Thank you all very much for reading the article!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *