DEV Community

Ahmad Jamaly Rabib
Ahmad Jamaly Rabib

Posted on • Edited on

Learning React: Virtual DOM

Hello there! 👋 I have recently learned about React's Virtual DOM. Instead of manipulating DOM directly like jQuery, React creates its own separate DOM. Let's see how it works.

Search for Virtual DOM

It is said that DOM read/write is slow, that's why the DOM element add/remove is slow. But the reality is DOM is really fast and the DOM element update/delete is similar to an object property adding or removing in JS. But why it seems that the DOM elements are not updating quickly is that the Render process in the browser after the DOM change.

Let's see the browser workflow first.

DOM browser workflow

In the top image we are seeing that the browser follows several steps to show the contents.

  • At first the browser receives the HTML file and then parses it using its render engine and creates a DOM tree. In the DOM tree the created HTML elements stay as nodes.
  • Like the HTML document, browser parse the CSS file contents using CSS parser. After parsing it is created similarly like the DOM for CSS. It is called CSSOM. Where the styles also stay as nodes.
  • These Parsed CSS and HTML together makes Render Tree. This Render Tree goes between a phase called layout. In this phase the Render tree coordinates are calculated and the elements are attached. So in this phase the render tree has all the information like the HTML, Style and the coordinates where it will paint the elements.
  • After that the painting phase is done based on the Render Tree nodes information.
  • And in the last stage the page is displayed to the end users.

So when we update anything in the DOM, the browser re-run the process again. All the HTML elements creation and positioning, CSS style information re-calculation is done again and after all this the page is repainted and displayed.

Although the modern browsers do these things in a very fast way, for better UX currently most of the web applications are made as SPA (Single Page Application).

While using SPA we have to handle lot's of DOM operations. Usually we can see that the web applications make 100's of DOM manipulation for single actions. 🥹😭😩

DOM manipulation for dev

When we will make these large amounts of DOM manipulation it is likely to be affected after some time in our application. But we have to do it anyway. 😞 So to minimize the effect in the performance we can try these two things.

  • Doing Batch Update 🤞
  • Try to do as less DOM operation as possible 😅

Let's see an example for the batch update here. Let's first create a HTML file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DOM</title>
</head>
<body>
    <div class="container">

    </div>
    <script src="./dom.js"></script>

</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now let's create the dom.js file.

let increment = 0;

let container = document.querySelector('.container');

// slow
while (increment < 50000) {
    increment++;
    container.innerHTML += ' ' + increment;
}

Enter fullscreen mode Exit fullscreen mode

In the dom.js file we can see that we are updating the container html for each increment in the while loop. So here the DOM operation is happening 50000 times. Which is a lot! And for this the page loads after a long time of waiting.

Now let's update the dom.js file. Let's create a blank array and push the numbers to the array for each increment.

let array = [];
let increment = 0;

let container = document.querySelector('.container');
// fast
while (increment < 50000) {
    array.push(++increment);
}

container.innerHTML = array.join(' ');
Enter fullscreen mode Exit fullscreen mode

So after all the elements are pushed to the array we can just put the array elements inside the container html using array.join(' '). So this only takes one DOM operation and the page loads instantly.

From this above example we can clearly see that due to too much DOM operation our page loads slowly. But with just simple modification we can achieve faster page load while getting the same output. This way is called batch update.

We can do batch update without using virtual DOM or React. But if we want to update DOM only for the element which is updated we need to use Virtual DOM.

To achieve this DOM should have two separate snapshots before updating the element and after updating the element. So that it can compare between these snapshots to check where to update.

But using DOM to do this task is difficult and it creates problems also it would take a lots of resource which could bring more problems. So React created Virtual DOM to achieve this feature.

Virtual DOM

So what is Virtual DOM?

It is a playground DOM for React, where React makes the changes.
It works similar like the browser DOM but there are no repainting steps here.
Only the JS elements are created/updated here. All the updates are done inside the virtual DOM, so there is no pressure on DOM to add/update the elements. 🤗

How does Virtual DOM work?
We can think of a Virtual DOM as a tree. Where the nodes are different components. If there is any change in the state of a node then it creates a new tree. In the new tree all the changed components and new components are newly created.

DOM compare

So, in this case React can compare the previous state and new state in the Virtual DOM to check which element in which position is changed. There is an efficient algorithm to compare the previous and later states. This algorithm is called the diffing or reconciliation algorithm.

Now let's see how normal DOM and virtual DOM works in case of updating elements with two examples using Reactive way.

Working with normal DOM
let's first create the HTML file.
index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DOM Tutorial</title>
    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <div class="container">
        <ul id="fruits"></ul>
        <br>
        <p>
            <input type="text" id="input">
        </p>
        <button id="button" onclick="addItem()">Add Item</button>
    </div>
    <script src="./dom.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

In the HTML file we can see that there is a ul list element area for elements.
Also there is a text input area and button to add items.
Now let's update in the dom.js file.

// Let's first declare the variables

const display = document.getElementById('fruits');
const button = document.querySelector('#button');

let fruits = ['mango', 'guava', 'apple', 'orange'];
Enter fullscreen mode Exit fullscreen mode

From the above code we can see that we have initialized the variable display for ul element and button. Also created an array for the initial lists of fruits.

Now let's create a function init and call to append and sort the fruits elements inside the display.

const init = function () {
    display.innerHTML = '';
    let liElements = '';
    fruits.sort().forEach(fruit => {
        let item = document.createElement('li');
        item.textContent = fruit;
        liElements += item.outerHTML;
    });
    display.innerHTML = liElements;
}

// Using appendChild
const init = function () {
    display.innerHTML = '';
    fruits.sort().forEach(fruit => {
        let item = document.createElement('li');
        item.textContent = fruit;
        display.appendChild(item);
    });
}

init();
Enter fullscreen mode Exit fullscreen mode

During button click we will call the addItem function. So now let's add this function.

const addItem = function () {
    fruits.myPush(document.getElementById('input').value);
}

Enter fullscreen mode Exit fullscreen mode

In the above code we are seeing that the new input value will be passed as a parameter in the myPush function.
We need to create the myPush function as a custom array prototype function.

Array.prototype.myPush = function (...a) {
    this.push(a[0]);
    init();
}
Enter fullscreen mode Exit fullscreen mode

In this Array.prototype.myPush function we are pushing the elements to the array and calling the init function.

So here the array is fruits and if we call the function using fruits.myPush('banana') then banana will be passed as parameter and it will be pushed to the fruits array. 🥳

Now let's see how the things gets re-rendered when we add any element in the DOM.

Checking DOM Render

Let's first open dev tools in Chrome. From the options->More tools select Rendering.

Select Rendering

Then from the Rendering tab check Paint Flashing.

Paint flashing

Then open the index.html file in the browser or use the live server plugin to open the page. Now let's see how the rendering is done.

Adding element on top area

Adding element in bottom area

From the rendering example we can see that all the elements are re-rendering and repainting when any element is added in any position. 😕

Now let's create the same project using React to take advantage of Virtual DOM. 😎
First let's create the index.html file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DOM Tutorial</title>
    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <div id="root"></div>

    <!-- Load React. -->
    <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>

    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>

    <!-- Load our React component. -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel" src="./fruits.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now let's create and modify the fruits.js file. We will see the state changes by tracking using the useState hook.

The React useState Hook allows us to track state in a function component.

const domContainer = document.querySelector('#root');

const Fruits = () => {
    const [fruit, setFruit] = React.useState('');
    const [fruits, setFruits] = React.useState(['mango', 'guava', 'apple', 'orange']);
};

Enter fullscreen mode Exit fullscreen mode

Above we can see that we created a component Fruits where we are destructuring the returned values from useState.

We have two const variables [fruit, setFruit] and [fruits, setFruits].

Here fruit and fruits are the current state and setFruit and setFruits are the functions that are used to update the states.

The initial state of fruit is an empty string, React.useState('').

And the initial state of fruits is an array of fruits, React.useState(['mango', 'guava', 'apple', 'orange']).

Now let's update the states in the Fruits component.

const domContainer = document.querySelector('#root');

const Fruits = () => {
    const [fruit, setFruit] = React.useState('');
    const [fruits, setFruits] = React.useState(['mango', 'guava', 'apple', 'orange']);

    return (
        <div className="container">
            <ul id="fruits">
                {fruits.sort().map((fruit, index) => (
                    <li key={index}>{fruit}</li>
                ))}
            </ul>
            <br />
            <p><input type="text" value={fruit} onChange={(e) => setFruit(e.target.value)} /></p>
            <button onClick={() => setFruits([...fruits, fruit])}>Add Item</button>
        </div>
    );
};

ReactDOM.render(<Fruits />, domContainer)
Enter fullscreen mode Exit fullscreen mode

From the return area of the component we can see that we are updating the states during key input and button click. Now let's run the project in the browser and see how the render is done while using React state.

Last Element

Top element

From the top result examples we can see that React efficiently updates the state and updates in that specific area in the DOM.

So from the examples of React Virtual DOM and browser DOM we can say that both are fast but Virtual DOM works in a more efficient way that's why it looks like it is working faster. 🙌😍

This is all from my today's learning. I will keep posting everyday from learning React. Thank you for reading. Bye 👋

I have started posting about my learning for the last few days. Here are the previous posts about React.

Top comments (0)