The Virtual DOM is not required

Virtual DOM is pure overhead

By Rich Harris by Svelte

Translator: kyrieliu

In the past few years, if you’ve been a user of any JavaScript framework, you’ve heard the phrase “Virtual DOM is fast,” which is often used to imply that Virtual DOM is faster than the real DOM. With this in mind, developers in the community ask me all kinds of questions, such as: How can Svelte be so fast without using the Virtual DOM?

We need to talk about this.

What is the Virtual DOM?

In most frameworks, developers build the entire app by building the Render function, like this simple React component:

function HelloMessage(props) {
  return (
  	<div className="greeting">
          Hello { props.name }
	</div>
  );
}
Copy the code

If you don’t use JSX, you can also express it in a different way:

function HelloMessage() {
  return React.createElement(
    'div',
    { className: 'greeting' },
    'Hello',
    props.name
  );
}
Copy the code

In both cases, the result is the same — an object representing the structure of the page is generated. This object is known as the Virtual DOM.

When the status of the app is updated (for example, when the property named name changes), a new Virtual DOM is generated. This is where the framework comes in: by comparing the differences between the old and new Virtual DOM and applying the least necessary changes to the real DOM.

Who first said “Virtual DOM is fast”?

Misconceptions about Virtual DOM performance date back to the official launch of React. In a 2013 talk called Rethinking Best Practices, Former React core team member Pete Hunt said:

This is actually extremely fast, primarily because most DOM operations tend to be slow. There’s been a lot of performance work on the DOM, but most DOM operations tend to drop frames.

Wait a minute! The Virtual DOM is not a substitute for DOM manipulation, but essentially complements it.

To say that Virtual DOM is faster, it must either be by comparing frameworks that use Virtual DOM with other frameworks that perform slightly less well, or by doing something that no one has done yet — rewriting the DOM rendering function:

onEveryStateChange((a)= > {
  document.body.innerHTML = renderMyApp();
});
Copy the code

Pete later clarified:

React is not magic. Just like you can drop into assembler with C and beat the C compiler, you can drop into raw DOM operations and DOM API calls and beat React if you wanted to. However, using C or Java or JavaScript is an order of magnitude performance improvement because you don’t have to worry… about the specifics of the platform. With React you can build applications without even thinking about performance and the default state is fast.

But that doesn’t seem to be the point…

So… Is Virtual DOM slow?

Is not slow. The reality is that Virtual DOM takes off quickly after avoiding certain pits.

React’s initial promise was that developers would be able to re-render the entire app every time the state changed without worrying about performance. Actually, it’s not. If React were as originally stated, there would be no optimizations like shouldComponentUpdate.

Even with shouldComponentUpdate, it’s a lot of work to update the entire app’s Virtual DOM at once. Not long ago, the React team introduced something called React Fiber, which breaks up updates into smaller chunks, meaning that updating the DOM in React doesn’t block the main thread for long periods of time. This, however, does not reduce the total effort or time spent updating the DOM.

Where does the overhead of the Virtual DOM come from?

Obviously, there is no such thing as a free diff. Before you can apply the differences to the real DOM, you need to compare the differences between the old and new Virtual DOM (hereinafter referred to as “snapshots”), which is what we call a DIff. Using the example of the HelloMessage component, suppose that the value of the name property changes from ‘world’ to ‘everybody’.

  1. Both snapshots contain only a single DIV element, which means you can keep the DOM node
  2. After enumerating all attributes of the unique div element in the two snapshots, no attributes are changed, added, or deleted. Both have a unique attribute className=’greeting’
  3. We look at the child elements of the div and see that the text node has changed, so we need to update the actual DOM

Only the third step has any real value. So far we have found that in the vast majority of update “cases” the basic organizational structure of the app does not change. If we could just skip to step three, we’d be much more efficient.

if (changed.name) {
  text.data = name;
}
Copy the code

Above is the update code generated by Svelte in most cases. Unlike traditional UI frameworks, Svelte captures how your app will change at build time, not run time.

Beyond the Diff

React and the diff algorithm it uses with its framework for the Virtual DOM are fast. You can say that most of the overhead comes from the components themselves. For example, you’d better not write this…

function TerribleComponent(props) {
  const value = expensivelyCalculateValue(props.foo);
  
  return (
  	<p>the value is { value }</p>
  );
}
Copy the code

This is bad because, by mistake, even though you didn’t change props. Foo, value will be recomputed every time you update it. But it is very common to make unnecessary calculations and allocations in ways that seem more beneficial:

function MoreRealisticComponent(props) {
  const [selected, setSelected] = useState(null);

  return (
    <div>
      <p>Selected {selected ? selected.name : 'nothing'}</p>

      <ul>
        {props.items.map(item =>
          <li>
            <button onClick={()= > setSelected(item)}>
              {item.name}
            </button>
          </li>
        )}
      </ul>
    </div>
  );
}
Copy the code

We’ve generated a set of virtual Li elements, each with its own line of click-event handlers, and the list is regenerated whenever the property changes (though not the props. Item). If you’re not looking for performance, you probably won’t optimize it. This is almost meaningless, because most of the time it all works very quickly. But, you know what would be faster? Don’t do it.

This default strategy of doing unnecessary work wears your app down, and by the time developers realize they need to optimize, there are often no obvious bottlenecks left to solve.

Svelte is designed to avoid that.

So why do most frameworks choose the Virtual DOM?

To be clear, the Virtual DOM is not a feature, but a strategy, and the ultimate goal of this strategy is to implement a declarative, state-driven UI development philosophy. The Virtual DOM is valuable because it allows developers to build an app without considering state changes and provides significant performance. Which means fewer bugs, more time to focus on the business…

But it turns out that we can implement a similar programming model without using the Virtual DOM, which is where Svelte comes in.