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.
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.
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. 🥹😭😩
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>
Now let's create the dom.js
file.
let increment = 0;
let container = document.querySelector('.container');
// slow
while (increment < 50000) {
increment++;
container.innerHTML += ' ' + increment;
}
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(' ');
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.
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.
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>
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'];
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();
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);
}
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();
}
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.
Let's first open dev tools in Chrome. From the options->More tools
select Rendering
.
Then from the Rendering tab check 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.
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>
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']);
};
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)
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.
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)