Hacker News new | ask | show | jobs
by arjvik 672 days ago
What benefit does the virtual DOM add?
6 comments

If you couldn’t efficiently batch updates, a vDOM could avoid repetitive updates in close succession, especially on IE6 (the browser React was designed for).

If you can control your app’s structure, it primarily adds significant increases in the RAM and CPU required for your app and slows load time because you are using a huge amount of JavaScript to emulate the carefully tuned C++ code built in to the browser. If you notice, most of the benchmarks from when React launched claiming performance wins were compared to heavyweight frameworks or complex jQuery plug-in combinations where a single user interaction might trigger cascading updates forcing the browser to rerender things which didn’t change along or to reflow multiple times in cascading update-measure-update chains. Pure DOM implementations were always faster, often by multiple orders of magnitude and once you could drop IE6, and then IE11, the DOM APIs and CSS were rich enough that much of the library code is now a net negative as well (e.g. people used to use complex code trying to build layouts which CSS grids solved).

It enabled a style of view library where you write immediate-mode type code that always recreates a whole component from scratch, versus having to write finicky code that both creates and then updates pieces of the page as state changes (dirty tracking, etc). Behind the scenes, you're creating the vDOM from scratch, which is diffed against the actual retained-mode DOM, and then only the pieces that are different are updated.
DOM interactions (read, writes) are synchronous, they're very slow, and it must happen on the main thread. This can cause the browser tab to freezing if access and updates aren't carefully "curated" (ie you don't want to read-check-then-write in a tight loop; or even write too often, even if it's the same value).

It can also simplify some stuff surrounding event handling (but that's not it's main goal I think)

So people wrote various ways to defer/batch/denounce updates.

Virtual DOM is a general solution/implementation. It's not the only one, but I think you always need at least a tiny runtime to avoid too much DOM access (ie Svelte, Solid JS are fairly minimal)

> but I think you always need at least a tiny runtime to avoid too much DOM access

Unless you use lit-html, which has a very efficient diffing algorithm that only updates the nodes that have changed.

How is that done without a vdom?
Lit-html uses template literals for that

https://lit.dev/docs/libraries/standalone-templates/

"lit-html lets you write HTML templates in JavaScript using template literals with embedded JavaScript expressions. lit-html identifies the static and dynamic parts of your templates so it can efficiently update just the changed portions."

A a high level there's not much difference between template literals and JSX, they are both syntax-sugary ways to represent trees of essentially function calls.

> efficiently update just the changed portions

Since actually applying each change to the real DOM is too slow, the only way to efficiently update is to batch changes and then apply the delta to the actual DOM.

That means we need to keep track of some state, namely the previously applied state and the current goal state, which you then compare.

Now, you may have noticed that we've just independently invented the concept of diffing. And the extra state that needed to be tracked can be given a spiffy name, like "virtual DOM", since it's like the DOM, but not the real thing.

So, I'm quite unconvinced by Lit-html's claim that they are able to efficiently mutate the DOM without using a vDOM anywhere.

Either their method is not efficient (for example it falls over for rapid updates), or there is a data structure under the hood that is analogous to a vDOM, even if they prefer to give that data structure a different name.

Oh well, we gotta thanks the great developers of Lit-html for making it transparent then. And a lot faster than React.

https://krausest.github.io/js-framework-benchmark/current.ht...

The virtual dom makes implementing a declarative templating system easer, and declarative templates are easer for a developer to reason about, and less error prone, than having to mutate the dom directly.

People often mistakingly describe the vdom as faster than the dom, this is incorrect. It would be faster than throwing away the whole components dom and rebuilding, so the same templating code building a new dom, rather than a vdom that's then diffed. Hand crafter mutations will be faster than a vdom diff, simply because the computer is doing les work, however much more error prone.

Virtual DOM is to classical JS what garbage collection is to malloc and free.

Garbage collection is less efficient, but it is sometimes very difficult to figure out exactly when a piece of memory stops being used, which leads to use-after-free, double-free and memory leak bugs.

Same goes for classical UI approaches. In classical UI, most pieces of state are kept in at least two places, once in code and at least once in the DOM.

For example, in a shopping cart, the total might appear three times, implicitly in the code (as a function that sums the prices of all the items), once as the label of the "open cart" button in the navbar, and once as text in the "your cart" modal, which that button shows or hides. THe cart may be modifiable from different places, the cart modal itself, product pages, product collection pages, order history (when re-ordering recently purchased items) etc.

In the classical approach, you need to make sure that all modifications to the cart accurately change the state in all three places. You also need to ensure that if you remove a product from the cart using the modal and you're currently on a page that lets you order the product in any way, the "remove from cart" button on that page needs to turn back into "add to cart", and there may be hundreds of different such buttons, which the cart modal needs to handle somehow. It is very easy to make mistakes here and have the state in the code (array of products) fall out of sync with what the user sees on the page.

In React, there's just one array of products, one function to calculate the total, and a lot of places in the code that use this array. WHenever the array changes, the pieces of the page that rely on it automatically re-render, while everything else stays the same. There's no way for the UI and the array to fall out of sync, and there's no need to track where the array is being used and where it's being modified.

I don't quite get how this reactivity is only possible with a VDom, for example Svelte https://learn.svelte.dev/tutorial/updating-arrays-and-object... also allows you to update the UI on changes to the array.
To extend the malloc versus GC metaphor, Svelte here is like Rust, it has really good developer experience while still giving you most benefits of Virtual DOM.
> People often mistakingly describe the vdom as faster than the dom, this is incorrect.

You'll get better performance with _carefully crafted_ DOM access, but that's easier said than done, especially on a larger applications.

vDOM takes care of the "carefully crafted" part with some trade offs, especially if it also defers rendering and doesn't wccess the DOM on every update.

So yes, it's easier to write declarative UIs with it, but it's also there to address common performance issues with unchecked/eager DOM access. Even if you don't throw away the whole tree and insert a new one, it can be very slow. Just _reading_ from the DOM is slow _and_ everything stops while that's being done too.

Slows down your app too, sometimes. Depends how well you can work with and mutate a DOM, but if all things equal no VDOM is always faster cause no diffing.
A lot of people can benefit from offsetting mutations with rAF and dbl rAF and batching reads/writes (FastDOM), before needing or considering a VDOM. VDOM came to prominence because of REACT and then started becoming used even when it wasn't needed. It does serve a purpose and scenario when needed, tho
With vDOM I could say `x = JSX` then cache that in state, inserting it in multiple places. Switching to Solid you have to make sure to use `x = () => JSX` & there's some mental model adjustments since logic outside JSX isn't reactive