Wait, React isn't about virtual DOM?
Tags: react, javascript, webdev
Published: 2020-10-28 15:43:03

---
title: Wait, React isn't about virtual DOM?
published: true
description: If you choose React because of Virtual DOM, you shouldn't. The main idea behind it is different, and VDOM is like an implementation detail. Let's sort it out and compare to template-based libraries like Angular or Vue.
tags: react, javascript, webdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/rrv5lgqqmq2kpt4nuyqx.jpg
---

> _Short answer for those who don't want to read:_
>
> Functional programming is the true theme of React. Virtual DOM is just one of its consequences and isn't really a benefit. It is slower by definition, and there could be no DOM at all if you're using React for sound or 3D rendering. Choose React if it matches your mindset, not because it was faster a couple of years ago.

---

Let's start with the opposite of virtual DOM: the real DOM. We're going to use an uncomplicated Counter component, who's content HTML may look like this:

```html
<div>
  Count: 123
</div>
<div>
  <button type="button">Increment</button>
  <button type="button">Decrement</button>
<div>
```

Imaging how would you build it using plain JavaScript. Probably you'll go by one of these 2 ways: `createElement` or  `innerHTML`.

Creating elements manually is time consuming. Just buttons section is almost screen height:

```js
class Counter {
  /* rest of the code */

  renderButton(text, handleClick) {
    const button = document.createElement("button");

    button.setAttribute("type", "button");
    button.textContent = text;
    button.addEventListener("click", handleClick);

    return button;
  }

  renderButtons() {
    const buttons = document.createElement("div");
 
    buttons.append(
      renderButton("Increment", this.handleIncrement),
      renderButton("Decrement", this.handleDecrement),
    );

    return buttons;
  }
}
```

We need a `createElement` call per every node, to append all required children, etc. But having an element reference allows easy attaching event listeners.

`innerHTML` may look less, but needs ids/classes to assign listeners:

```js
class Counter {
  /* rest of the code */

  render() {
    this.container.innerHTML = `
      <div>
       Count: <span id="label">${this.count}</span>
      </div>
      <div>
       <button type="button" id="btn-inc">Increment</button>
       <button type="button" id="btn-dec">Decrement</button>
      <div>
    `;

    this.label = document.getElementById("label");
    this.btnIncrement = document.getElementById("btn-inc");
    this.btnDecrement = document.getElementById("btn-dec");

    this.btnIncrement.addEventListener("click", this.handleIncrement);
    this.btnDecrement.addEventListener("click", this.handleDecrement);
  }
}
```

We use less lines on setting attributes, but more on searching for elements for future updates and adding excess classes.

Of course, no one wants to do such work manually. That's why we've got UI libraries like Angular, Vue, Svelte, and others. These 2 options of building a Counter are roughly what we get in a template-based library.

The `innerHTML` is somewhat the original AngularJS: our bundle contains the template string and the engine runs on the client by parsing this template, finding slots for data and expressions inside it, inserting it into the page, and attaching methods as listeners. Larger bundle size and additional load on the browser are downsides of this approach.

The `createElement` is like modern Svelte/Ivy, where the template is parsed/compiled build time into a set of document manipulation commands, so no string embedding or runtime is required. We get less bundle overhead and the code is optimized specifically for our component, but at a cost of loosing features on the client.

Looks not that complicated, right?

That's because we forgot the part with the template language: conditions and repeaters. All the good stuff anyone can't really use templates without. Imagine adding that to our `Counter` code: instead of a simple `innerHTML` we need to parse the string and "run" dynamic parts. What if condition changes later, how we're going to find out about that? Will we re-render only dynamic parts or the entire component? The codebase will be complicated and much larger.

But there's more. What if we need to use a custom Button component?

```html
<div
  component="Button"
  label="Increment"
  onclick="this.handleIncrement"
></div>
```

It's doable. Just create this `div` element and pass it as a container to a class registered as `Button`. But it must be registered in advance:

```js
const Button = require("../components/button.js");

UI.registerComponent("Button", Button);
```

Attributes should be parsed to distinguish between `div`'s HTML attributes and `arguments` to the `Button`. Basically the `div` is now a sub-tree and should operate on its own.

But what if we want to use not just a `Button`, but one of several components conditionally? 

```html
<div
  components="this.isLoading ? 'Button' : 'Image'"
  label="Increment"
  onclick="this.handleIncrement"
></div>
```

It isn't a simple mapping anymore, but an expression, which needs to be compiled appropriately with JS executed at right times and the component instances destroyed/created. And those attributes may be re-parsed every time, because `label` could be an argument for a `Button`, but not for an `Image`.

Think about the original AngularJS with all its scopes, hierarchies, transclusion, etc. Complexity goes nuts with dynamically nested templates. That's why `ng-include` was static and we couldn't just render `any` template based on business logic.

But there's more. What if we need to build a component on the fly? Is it even possible, if template parsing and code emitting happens at build time?

We could get a team of super-stars and try to build an engine or a compiler providing all those features, but the point is that almost every feature influences the rules by which you will write template and/or logic because of it's complexity. And you're still somewhat restricted by a template.

---

Now, let's abstract away and get into a **functional data driven land**.

Everything in the world could be represented as a result of a function call and its arguments:

```
function(args) ⟶ anything
```

Inside a function you can do any kind of things including calling other functions (composition). We had functions (methods) before in the `Counter` class too, but with different insides.

Instead of only producing a result, methods alter existing state (in our case document elements with `append` or `innerHTML`), especially on counter updates. In functional world it is forbidden and passed arguments are immutable. Even if we pass a container `div` into a function, it can't _add_ nodes here. Instead, we should rely only on the returned value. And in case of an update, to re-execute the function and to get next result out of it.

As we draw a UI, return values should describe it somehow. We could return an `HTMLElement`, but it has imperative mutable interface. Anyway, manually using document APIs is time-consuming as we know. Let's revisit HTML of our component: 

```html
<div>
  Count: 123
</div>
```

It's not that different from a JavaScript object.

```js
const html = { element: "div", children: [
  "Count: 123"
] }
```

An object notation is more verbose for sure, as a general language should be to a DSL. But we could easily build such objects ourselves without mutating anything (and parsing a template). We could even reduce boilerplate by implementing a little helper:

```js
function element(name, ...children) {
  return { element: name, children };
}

const ui = element("div",
  "Count: 123"
)
```

Moreover, objects can reference functions, so we don't need a map of pre-registered components:

```js
function CounterLabel(children) {
  return element("div",
    "Count is ",
    element("span", ...children)
  );
}

const ui = element(CounterLabel, 0);
```

And the result would be:

```js
const counterLabelResult = {
  element: "div",
  children: [
    "Count is ",
    { element: "span", children: [0] }
  ]
};

const ui = { element: CounterLabel, children: [0] };
```

Now we need somebody to recursively go through this object tree (UI description) calling functions (our components) inside `element` properties.

One more thing. A real-world UI needs to _react_ on events like button click. How would we know to re-execute the function? Let's just pass a _callback_ for this, which could be used, for example, as a click handler:

```js
function FancyButton(children, refresh) { ... }
```

Assume that we've made such function that processes the object tree recursively, simultaneously passing the callback. We will call it `getDescriber`:

```js
function getDescriber(component) {
  /*
   const describeUI = ...
   ...
  */
  return refresh => describeUI(component, refresh);
}

const describer = getDescriber(Counter);
```

`describer` accepts a `refresh` callback and outputs a complete UI description as a nested object of strings, numbers, and arrays (basically, a JSON).

The only part missing is a function to read this description and emit DOM elements into the document. We will call it `render`, and assume that we have its implementation already done by somebody:

```js
function render(describer, mountNode) { ... }

render(describer, document.getElementById("root"));
```

Let's recap. We have 2 parts and just 3 functions:

1. `element(name, ...children)` and `getDescriber(component)` [react]
2. `render(describer, mountNode)` [react-dom]

Part #1 consists of `element` and `getDescriber` used together to make a description. Part #2 is only `render`, which is used exclusively when you need to get actual HTML elements. Both parts are independent. The only thing that connects them together is the _structure_ of the _description_. `render` expects a nested object with `element` and `children` properties. That's all.

Part #1 could do whatever it wants: generate functions/closures of the fly and execute them, check conditions of any complexity... Instead of adding another complicated template language syntax you just use the whole power of JavaScript. As long as it outputs required objects, no downsides or limits of template engines exist.

You can call this object description a `virtual DOM`, but only if you're using that particular `render` function from above. We can make `render` that instead of calling `document.createElement` will... play sounds! We may interpret the description as we want. Is it DOM anymore?

As you might guess Part #1 is `react` and Part #2 is `react-dom`.

---

React isn't about virtual DOM. It's about abstracting away physical body of your structured data and helping you to update that structure over time. You work on the structure and data with React, some one else will materialize that structure later. Web pages do have a structure, so it's convenient for React to have a materializer for DOM. If Facebook was a music company, maybe React would have shipped with `react-midi` instead.

React is about functional approach, abstraction, flexibility, and unidirectional flow. Virtual DOM is a consequence of using it in a browser. Reconciliation and partial updates aren't fast. Manually crafted set of DOM manipulations is more effective by definition, and compilators can do this for templates. But React allows you to think different about UI, not as about strings and markup. React allows you to use functional composition for UI structure and a real language for UI logic. It's a mindset thing.