Understanding Virtual DOM that powers ReactJS and VueJS

October 28, 2023

You want to understand the main core concept behind React, VueJS ? You are in the right place ! Let’s dive into making our own virtual DOM using simple JavaScript.

Guide to make your own virtual DOM

To create a Virtual DOM, we need to understand the composition of the DOM structure, which consists of HTML nodes.

A HTML node is made of:

  • Tag: div, span, p, h1,…
  • Attributes: class, id, label, value,…
  • Children nodes: contains zero, one or more nodes.

Now let’s create the virtual DOM using JavaScript. First we need to create a function representing the anatomy of a HTML node in a JSON structure.

function createVirtualNode(tag, attributes, children) {
  return {
    // div, span, p, h1,...
    tag: tag || "",
    // class, id, label, value,...
    attributes: attributes || {},
    children: children || [],
  };
}

Now let’s determine the HTML structure we want to build:

<div id="header">
  <p>Hello <span class="bold text-pink">everybody</span> !</p>
  <img
    src="https://images.unsplash.com/photo-1414235077428-338989a2e8c0"
    alt="restaurant"
    style="width: 300px;"
  />
</div>

Ok let’s use our function createVirtualNode to represent this HTML structure. Read and analyze the following carefully (yeah it’s hard to read).

// <div id="header"></div>
const virtualDOM = createVirtualNode("div", { id: "header" }, [
  // <p>Hello</p>
  createVirtualNode("p", {}, [
    "Hello ",
    // <span class="bold text-pink">everybody</span>
    createVirtualNode("span", { class: "bold text-pink" }, ["everybody"]),
    " !",
  ]),
  // <img src="..." alt="restaurant" style="width: 300px;" />
  createVirtualNode("img", {
    src: "https://images.unsplash.com/photo-1414235077428-338989a2e8c0",
    alt: "restaurant",
    style: "width: 300px;",
  }),
]);

console.dir(virtualDOM, { depth: null });

Let’s see what our virtual DOM looks like:

// console.dir(virtualDOM, { depth: null })
{
  tag: 'div',
  attributes: { id: 'header' },
  children: [
    {
      tag: 'p',
      attributes: {},
      children: [
        'Hello ',
        {
          tag: 'span',
          attributes: { class: 'bold text-pink' },
          children: [ 'everybody' ]
        },
        ' !'
      ]
    },
    {
      tag: 'img',
      attributes: {
        src: 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0',
        alt: 'restaurant'
      },
      children: []
    }
  ]
}

Wow… we just made our first virtual DOM JSON structure.

But what do we do now with this JSON structure now ? They are 2 big usages of this virtual DOM in the real world:

  1. Render it to the real DOM
  2. Server side rendering: Generating the corresponding HTML server-side and send an HTML page

Virtual DOM Client side: mounting into the real DOM

Let’s render it to the real DOM first by creating the render function:

function renderDOMNode({ tag, attributes, children }) {
  const node = document.createElement(tag);

  for (const attributeKey in attributes) {
    node.setAttribute(attributeKey, attributes[attributeKey]);
  }

  for (const childrenNode of children) {
    if (typeof childrenNode === "string") {
      node.appendChild(document.createTextNode(childrenNode));
    } else {
      node.appendChild(renderDOMNode(childrenNode));
    }
  }

  return node;
}

Let’s use it !

<html>
  <head>
    <title>Virtual DOM rendered client-side</title>
  </head>
  <body>
    <div id="app" />
  </body>
</html>
const node = renderVirtualNode(virtualDOM);

document.getElementById("app").appendChild(node);

Virtual DOM Server side: render to string for Server side rendering (SSR)

function renderVirtualNodeToString({ tag, attributes, children }) {
  let node = `<${tag}`;

  for (const attributeKey in attributes) {
    node += ` ${attributeKey}="${attributes[attributeKey]}"`;
  }

  if (children.length === 0) {
    node += "/>";
  } else {
    node += ">";
    for (const childrenNode of children) {
      if (typeof virtualChildNode === "string") {
        node += virtualChildNode;
      } else {
        node += renderVirtualNodeToString(childrenNode);
      }
    }
    node += `</${tag}>`;
  }
  return node;
}

Now let’s see how to use our VirtualDOM server side using NodeJS:

import http from "http";

const renderedHTML = renderVirtualNodeToString(virtualDOM);

const server = http.createServer((request, response) => {
  response.writeHead(200, { "Content-Type": "text/html" });
  response.end(`
      <html>
      <head>
        <title>Virtual DOM rendered server-side</title>
      </head>
      <body>
        <div id="app">${renderedHTML}</div>
      </body>
      </html>
    `);
});

const PORT = 3000;

server.listen(PORT, () => {
  console.info(`Server is running at http://localhost:${PORT}`);
});

What you see above demonstrates the power and flexibility of a Virtual DOM. By maintaining a representation of the DOM in memory, frameworks like React and Vue can optimize updates and perform advanced features such as server-side rendering, which allows for improved performance and SEO benefits.