DEV Community

Cover image for Understanding React Virtual DOM Internally
Shivam Yadav
Shivam Yadav

Posted on

Understanding React Virtual DOM Internally

hey reader its me shivam

and today i am all researching about DOM and how React works, why am i learning it, what benefit do i get after learning it

so with this many thoughts i really got to know about what is Virtual DOM and how things internally work in React

so i am not going to bore u or anything so lets start


First let see the problem statement

so population is growing ( people are doing too much sex )

as well the user of web has also grown

and in web application there is constant changes happening like:

  • notification
  • message loading
  • forms validation
  • comments
  • likes
  • realtime updates

etc

if browser update the page directly for every changes the application would become slow and kinda heavy

this is exactly the problem faced by facebook developer

so they created REACT and react's virtual DOM was designed to solve this problem

now this was the story for React that u have heard many time

now lets start understanding of REACT working


The Problem : Direct DOM Manipulation is Expensive

before understanding Virtual DOM, first lets understand Real DOM


what is real DOM

the real DOM is the browser tree structure representing the HTML in element

Example:

<div>
  <h1>Hello</h1>
  <button>Click</button>
</div>
Enter fullscreen mode Exit fullscreen mode

Browser converts this into a tree-like structure:

div
 ├── h1
 └── button
Enter fullscreen mode Exit fullscreen mode

The browser basically create a tree from your HTML

and every single element becomes a node inside memory

the issue is:

updating the real DOM is Expensive ( now this sound out of the box but it is Expensive )

things like:

  • repainting
  • reflow
  • layout recalculation
  • css recalculation

Even changing a small thing in HTML can cause heavy loading

see for example there are 100's of node/html element now u want to add one more element at 98

now what browser DOM will do?

he will just reload everything create a new DOM tree and while creating it will add the new added element at 98'th place

as u can see the unnecessary loading of all 97 element is doing

parsing of HTML, then css calculation, applying

it sound heavy then imagine the actual implementation

and yes browser is fast...

but modern UI are even faster 💀


this is where React enters into the picture

imagine a social media feed

every second:

  • likes increase
  • message arrive
  • notification popup
  • comments loading

are happening now with each updating, performance would collapse

React solve this by:

  1. creating a Virtual DOM tree
  2. comparing changes effectively
  3. updating only changed node

yes i know sound technical but lets see something here


What is Virtual DOM?

As the name implies, the Virtual DOM is a much lighter replica of the actual DOM in the form of objects.

it is basically a lightweight JavaScript representation of UI

this is how the object looks like

{
  type: "div",
  props: {
    children: [
      {
        type: "h1",
        props: {
          children: "Hello"
        }
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

The virtual DOM can be saved in the browser memory and doesn’t directly change what is shown on the user’s browser.

Implemented by several other frontend frameworks, like Vue, React’s declarative approach is unique.

this is the cool part

nothing is directly touching the browser DOM instantly

React first prepares everything virtually

like writing rough work before final answer 😭


this is what React do ( Master React )

if u understand this u will get almost 90% of React working

React is working in three stages


1) Rendering

when ever a component state or props changes, React re-render the component UI to generate a new Virtual DOM tree

this tree is kinda representation of UI component in plain JavaScript object

it is kinda same as browser DOM but omitting the browser detail

kinda help being fast

things to be known:

there are three DOM now

  1. the previous Virtual DOM
  2. the new updated Virtual DOM
  3. the actual browser DOM

2) Diffing

once the new Virtual DOM is created now React perform the diffing process

it start comparing the new tree with the previous version to identify exactly which element have changed

in short it find out diff between new and old Virtual DOM

the comparison matches element types using keys and list item helps on quickly pinpointing changes

now it will only isolate the difference, significantly minimizing the number of update needed


Example of Diffing

Old tree:

<h1>0</h1>
Enter fullscreen mode Exit fullscreen mode

New tree:

<h1>1</h1>
Enter fullscreen mode Exit fullscreen mode

React notices:

  • h1 tag is same
  • Only text changed

So React updates only text content.

Minimal Real DOM Update

Instead of rebuilding everything:

<h1>0</h1>
Enter fullscreen mode Exit fullscreen mode

becomes:

<h1>1</h1>
Enter fullscreen mode Exit fullscreen mode

Only the changed node updates.

This is the key optimization.


Diagram : Diffing Process

Old Virtual DOM
      ↓
Compare
      ↓
New Virtual DOM
      ↓
Find Difference
      ↓
Update Only Changed Node
Enter fullscreen mode Exit fullscreen mode

things to be known:

  1. this process is also called reconciliation
  2. in simple words it does comparing two Virtual DOM trees to find what changed

some people think Virtual DOM itself is the magic

but actually reconciliation is the real beast behind React 😭

Virtual DOM is just the data structure

reconciliation is the brain


3) Patching

so now after knowing the changes React generate a minimal instruction and applies the changes to the actual DOM

this process is known as patching

things to be known:

  1. it is also known as commit phase

instead of re-rendering the entire DOM tree, only the modified parts are updated

help in reducing performance overhead with full DOM updates

and this is why React apps feel smooth most of the time

unless u forgot dependency array in useEffect

then may god help your CPU 💀


NOW lets deep dive in more of rendering

How re-rendering impacts performance

DOM operations are very fast, light operations.

However, when the app data changes and triggers an update, re-rendering can be expensive.

Let’s simulate re-rendering a page with the JavaScript code below:

const update = () => {
 const element = `
  <h3>JavaScript:</h3>
  <form>
   <input type="text"/>
  </form>
  <span>Time: ${new Date().toLocaleTimeString()}</span>
 `;

 document.getElementById("root1").innerHTML = element;
};

setInterval(update, 1000);
Enter fullscreen mode Exit fullscreen mode

The DOM tree representing the document looks like the following:

now this setInterval() callback is triggering the rendering in each second as can see in the GIF

so basically the DOM element are getting rebuild and painted on each update(second)

and the text input in UI also loses its state due to the re-render

soo as u can see the problem clearly like really clearly

when ever the UI update the things written on input is getting wiped out like it was never there

basically the state is getting changed

so to solve this problem different JS framework provide different solution

React implement the concept of the Virtual DOM

As the name implies, the virtual DOM is a much lighter replica of the actual DOM in the form of objects.

this is how the object looks like

The virtual DOM can be saved in the browser memory and doesn’t directly change what is shown on the user’s browser.


how React implement the Virtual DOM

To understand the Virtual DOM strategy, we need to understand the two major phases that are involved:

  • rendering
  • reconciliation

When we render an application UI, React creates a Virtual DOM tree representing that UI and stores it in memory.

On the next update, or in other words, when the data that renders the app changes, React will automatically create a new Virtual DOM tree for the update.

To further explain this, we can visually represent the Virtual DOM as follows:

The image on the left is the initial render.

As the Time changes, React creates a new tree with the updated node, as seen on the right side.

Remember:

the Virtual DOM is just an object representing the UI

so nothing gets drawn on the screen directly.

After React creates the new Virtual DOM tree, it compares it to the previous snapshot using a diffing algorithm called reconciliation to figure out what changes are necessary.

After the reconciliation process, React uses a renderer library like ReactDOM, which takes the different information to update the rendered app.

This library ensures that the actual DOM only receives and repaints the updated node or nodes.

As seen in the image above, only the node whose data changes gets repainted in the actual DOM.

The GIF below further proves this statement:


The React Diffing Process

When React diffs two Virtual DOM trees, it begins by comparing whether or not both snapshots have the same root element.

If they have the same elements, like in our case where the updated nodes are of the same span element type, React moves on and checks the attributes.

If attributes are same then React moves deeper into child nodes.

Upon seeing that the Time text node has changed, React will only update the actual node in the real DOM.


Different Element Type Case

if both snapshots have different element types, React will destroy the old DOM nodes and build a new one.

For instance:

<span>Time: 04:36:35</span>
Enter fullscreen mode Exit fullscreen mode

becomes

<div>Time: 04:36:38</div>
Enter fullscreen mode Exit fullscreen mode

now React cannot optimize this because the element type itself changed

so React rebuilds that section


Example using React State

import { useState } from "react";

const App = () => {
 const [open, setOpen] = useState(false);

 return (
  <div className="App">
   <button onClick={() => setOpen((prev) => !prev)}>toggle</button>

   <div className={open ? "open" : "close"}>
    I'm {open ? "opened" : "closed"}
   </div>
  </div>
 );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Updating the component state re-renders the component.

However, as shown below, on every re-render, React knows only to update:

  • the class name
  • the text that changed

This update will not hurt unaffected elements in the render.

and this is why React feel smooth

otherwise every button click would feel like windows XP restarting 😭


How React Diffs Lists

When we modify a list of items, how React diffs the list depends on whether the items are added at the beginning or the end of the list.

Consider this list:

<ul>
  <li>item 3</li>
  <li>item 4</li>
  <li>item 5</li>
</ul>
Enter fullscreen mode Exit fullscreen mode

On the next update, lets append an item 6 at the end:

<ul>
  <li>item 3</li>
  <li>item 4</li>
  <li>item 5</li>
  <li>item 6</li>
</ul>
Enter fullscreen mode Exit fullscreen mode

React compares the items from the top.

It matches:

  • item 3
  • item 4
  • item 5

and only insert item 6

This computation is straightforward for React.

easy work for React


But now comes the real issue

what if we prepend item 2?

<ul>
  <li>item 2</li>
  <li>item 3</li>
  <li>item 4</li>
  <li>item 5</li>
</ul>
Enter fullscreen mode Exit fullscreen mode

Similarly, React compares from the top, and immediately realizes that item 3 doesn’t match item 2 of the updated tree.

now React compares from top and sees:

item 3 ≠ item 2
Enter fullscreen mode Exit fullscreen mode

so React think:

"bro everything changed 💀"

and it may rebuild the whole list


This is why React Keys Matter

Instead of rebuilding the entire list, we want the DOM to compute minimal operations by only prepending item 2.

React lets us add a key prop to uniquely identify items.

<ul>
  <li key="3">item 3</li>
  <li key="4">item 4</li>
  <li key="5">item 5</li>
</ul>

<ul>
  <li key="2">item 2</li>
  <li key="3">item 3</li>
  <li key="4">item 4</li>
  <li key="5">item 5</li>
  <li key="6">item 6</li>
</ul>
Enter fullscreen mode Exit fullscreen mode

With keys, React understands:

  • item 2 is new
  • item 6 is new
  • existing items are same

As a result, it would work to preserve the items that are already available and add only the new items in the DOM.

so only minimal updates happen

this is why senior developers scream:

"PUT KEYS IN LIST"

now u know why 😭


How is Virtual DOM Different from Real DOM?

A common misconception is that the Virtual DOM is faster than or rivals the actual DOM.

However, this is untrue.

In fact, the Virtual DOM’s operations support and add on to those of the actual DOM.

Essentially, the Virtual DOM provides a mechanism that allows the actual DOM to compute minimal DOM operations when re-rendering the UI.

For example, when an element in the real DOM is changed, the DOM will re-render the element and all of its children.

When it comes to building complex web applications with a lot of interactivity and state changes, this approach is slow and inefficient.

Instead, in the rendering process, React employs the concept of the Virtual DOM, which conforms with its declarative approach.

Therefore, we can specify what state we want the UI to be in, after which React makes it happen.

After the virtual DOM is updated, React compares it to a snapshot of the virtual DOM taken just before the update, determines what element was changed, and then updates only that element on the real DOM.

This is one method the Virtual DOM employs to optimize performance.

Real DOM Virtual DOM
Heavy Lightweight
Direct browser updates JS object
Expensive updates Efficient diffing
Causes repaint/reflow Minimal updates
Hard manual manipulation Declarative updates

The Virtual DOM abstracts manual DOM manipulations away from the developer, helping us write more predictable code so that we can focus on creating components.

Thanks to the Virtual DOM, you don’t have to worry about state transitions.

Once you update the state, React ensures that the DOM matches that state.

For instance, in our last example, React ensures that on every re-render, only Time gets updated in the actual DOM.

Therefore, we won’t lose the value of the input field while the UI update happens.


Fun Fact : Virtual DOM Isn’t Always the Fastest

I know, bold statement.

But sometimes manual DOM updates are faster, especially for static-heavy pages.

Libraries like Svelte skip the Virtual DOM altogether, compiling updates directly to the DOM at build time.

React trades a bit of raw speed for:

  • developer happiness
  • maintainability
  • scalability

And lets be honest

we love that.


Think of Virtual DOM Like a Draft System

Think of the Virtual DOM as your favorite todo app.

You write down what changed.

Then review and edit before committing.

Only after reviewing, you update the real list.

Same with React.

  • Draft first (Virtual DOM)
  • Diff and plan (reconciliation)
  • Then update reality (Real DOM)

The Virtual DOM is often credited for React’s speed, but the real hero behind the scenes is reconciliation.

it’s what determines:

  • what to update
  • when to update
  • how to sync efficiently

A poor reconciliation strategy can significantly impact your app’s performance.


in the end

is it really fast?

i asked this from my senior and he said at initial level u will see it as just extra working:

  • rendering
  • diffing
  • patching

but when we start talking about millions of nodes handling then the magic of React appears

at small level u might not notice the difference much

but at scale?

that architecture matters a lot

and that all from my side

if u find this blog interesting and knowledgeable please feel free to drop a like and comment down what can be better

i am really in need of improving

bye

see u in the next blog

till then bye bye

Top comments (0)