A little about how the virtual DOM works in React

image

Real or real DOM

DOM stands for Document Object Model (document object model). In simple terms, the DOM is the representation of the user interface (UI) in an application. Whenever the UI changes, the DOM is also updated to reflect those changes. Frequent DOM manipulation has a negative impact on performance.

What makes DOM manipulation slow?

DOM is a tree-like data structure. Therefore, changes and updates to the DOM itself are quite fast. But after the change, the updated element and all of its descendants (children) must be re-rendered (rendered) to update the application UI. Re-rendering is a very slow process. Thus, the more UI components we have, the more expensive DOM updates are in terms of performance.

DOM manipulation is at the heart of the modern interactive web. Unfortunately, they are much slower than most JavaScript operations. To make matters worse, many JavaScript frameworks update the DOM more often than necessary.

Let’s say we have a list of 10 items. We are changing the first item. Most frameworks will rebuild the entire list. That’s 10 times more work than it takes! Only 1 element has changed, the remaining 9 remain the same.

Rebuilding a list is an easy task for the browser, but modern websites can do a tremendous amount of DOM manipulation. Therefore, an ineffective update often becomes a serious problem. To solve this problem, the React team popularized something called the virtual DOM (VDOM).

Virtual DOM

In React, for every real DOM object (hereinafter referred to as RDOM), there is a corresponding VDOM object. VDOM is an object representation of RDOM, a lightweight copy of it. VDOM contains the same properties as RDOM, but cannot directly affect what is displayed on the screen.

Virtual DOM (VDOM) is a programming concept where an ideal or “virtual” representation of a UI is stored in memory and synchronized with the “real” DOM used by libraries such as ReactDOM. This process is called reconcilation

RDOM manipulations are slow. VDOM manipulations are much faster as they are not displayed (drawn) on the screen. Manipulating VDOM is similar to working with a project (or plan) of a building before starting its construction.

Why is VDOM faster?

When new elements are added to the UI, a VDOM is created in the form of a tree. Each element is a node of this tree. When the state of any element changes, a new tree is created. This new tree is then diffed against the old one.

The most efficient method for making changes to the RDOM is then calculated. The purpose of these calculations is to minimize the number of operations performed on the RDOM. This reduces the overhead associated with updating the RDOM.

The images below show the virtual DOM tree and the reconciliation process.

The nodes that have been updated are marked in red. These nodes represent UI elements whose state has changed. After that, the difference between the previous and current versions of the virtual DOM tree is calculated. The entire parent subtree is then re-rendered to represent the updated UI. Finally, this updated tree is used to update the RDOM.

How does React use VDOM?

Now that we’ve covered what VDOM is, it’s time to talk about how it is used in React.

1. React uses the Observer design pattern and reacts to state changes

In React, every piece of UI is a component, and almost every component has a state. When the component’s state changes, React updates the VDOM. After updating the VDOM, React compares its current version with the previous one. This process is called diffing.

After detecting objects that have changed in the VDOM, React updates the corresponding objects in the RDOM. This significantly improves performance over direct DOM manipulation. This is what makes React a high-performance JavaScript library.

2. React uses a batch RDOM update mechanism

It also has a positive effect on performance. The named mechanism assumes sending updates in the form of packets (set, series) instead of sending a separate update for each state change.

UI rerendering is the most expensive part, React provides point and batch redrawing of the RDOM.

3. React uses an efficient difference-finding algorithm

React uses a heuristic O (n) (linear) algorithm based on two assumptions:

  1. Two elements of different types lead to the construction of different trees

  2. The developer can ensure the stability of elements between renders by means of a prop key (key)

In practice, these assumptions are correct in almost all cases.

When comparing two trees, React starts at the root elements. Further operations depend on the types of these elements.

Elements of different types

  • If the root elements are of different types, React destroys the old tree and builds a new one from scratch.

  • All old DOM nodes are destroyed along with the old tree. Component instances get componentWillUnmount()… When building a new tree, new DOM nodes are embedded in the DOM. Component instances get first UNSAFE_componentWillMount()then componentDidMount()… Any condition associated with the old tree is lost

  • Any components that are children of the root are unmounted and their state destroyed. For example, when comparing:

<div>

 <Counter />

</div>

<span>

 <Counter />

</span>

Old Counter will be destroyed and recreated.

Elements of the same type

When comparing two elements of the same type, React looks at the attributes of those elements. DOM nodes persist, only their attributes change. For example:

<div className="before" title="stuff" />

<div className="after" title="stuff" />

After comparing these elements, only the attribute will be updated className

After processing a DOM node, React recursively iterates over all of its descendants.

Recursively iterating over children

By default, React iterates over two lists of children of a DOM node and generates a mutation when a difference is found.

For example, when adding an element to the end of a list of children, converting one tree to another works well:

<ul>

 <li>первый</li>

 <li>второй</li>

</ul>
<ul>

 <li>первый</li>

 <li>второй</li>

 <li>третий</li>

</ul>

React “sees” that both trees have <li>первый</li> and <li>второй</li>, skips them and inserts at the end <li>третий</li>

Usually, inserting an item at the beginning of a list is bad for performance. For example, converting one tree to another will not work well in this case:

<ul>

 <li>первый</li>

 <li>второй</li>

</ul>
<ul>

 <li>нулевой</li>

 <li>первый</li>

 <li>второй</li>

</ul>

React won’t be able to figure out that <li>первый</li> and <li>второй</li> remained the same and mutates each element.

Using keys

To solve this problem, React provides the (prop) attribute key… When children have keys, React uses them to compare the children of the current and previous nodes. For example, adding keys to the elements from the last example will make the tree transformation much more efficient:

<ul>

 <li key="1">первый</li>

 <li key="2">второй</li>

</ul>
<ul>

 <li key="0">нулевой</li>

 <li key="1">первый</li>

 <li key="2">второй</li>

</ul>

Now React knows that the element with the key 0 is new, and items with keys 1 and 2 old.

In practice, identifiers are usually used as keys:

<li key={item.id}>{item.name}</li>

If there are no identifiers, you can always add them to the data model or create a hash based on any part of the data. Keys must be unique among neighboring elements, not globally.

In extreme cases, array indices can be used as keys. This only works well if the order of the elements remains the same. Changing the order of items will be slow.

Changing the order of elements when using indexes as keys can also lead to problems with the state of the elements. Component instances are updated and reused based on keys. If the key is an index, moving the item will change the key. As a result, component state for things like an unmanaged input field can get mixed and updated in unexpected ways.


In simple terms: “You tell React what state the UI should be in, and it ensures that the DOM conforms to that state. The advantage of this approach is that, as a developer, you don’t need to know exactly how attribute changes, event handling, and DOM updates occur. “

All of these things are abstracted by React. All you have to do is update the component’s state, React will take care of the rest. This provides a very good development experience.

Since “virtual DOM” is more of a pattern than a specific technology, this concept can mean different things. In the React world, “virtual DOM” is usually associated with React elements, which are objects that represent the user interface. However, React also uses internal objects called fibers. These objects store additional information about the component tree. Fiber Is a new reconciliation engine introduced in React 16. Its main purpose is to provide incremental VDOM rendering.

What does VDOM look like?

The name “virtual DOM” makes the concept a bit magical (mystical). In fact, the VDOM is just a regular JavaScript object.

Let’s imagine we have a DOM tree like this:

This tree can be represented as an object like this:

const vdom = {

 tagName: 'html',

 children: [

   { tagName: 'head' },

   {

     tagName: 'body',

     children: [

       {

         tagName: 'ul',

         attributes: { class: 'list' },

         children: [

           {

             tagName: 'li',

             attributes: { class: 'list_item' },

             textContent: 'Элемент списка',

           }, // конец li

         ],

       }, // конец ul

     ],

   }, // конец body

 ],

} // конец html

This is our VDOM. Like RDOM, it is an object representation of an HTML document (markup). However, since it is just an object, we can freely and often manipulate it without touching the RDOM unless absolutely necessary.

Instead of using one object for the entire document, it is more convenient to divide it into small sections. For example, we can extract a component from our object listcorresponding to an unordered list:

const list = {

 tagName: 'ul',

 attributes: { class: 'list' },

 children: [

   {

     tagName: 'li',

     attributes: { class: 'list_item' },

     textContent: 'Элемент списка',

   },

 ],

}

VDOM under the hood

Now let’s talk about how VDOM solves the performance and reusability issue.

As we found out earlier, VDOM can be used to detect specific changes that need to be made to the DOM. Let’s go back to our unordered list example and make the same changes that we did with the DOM API.

First of all, we need a copy of the VDOM with the changes that we want to implement. Since we don’t need to use the DOM API, we can just create a new object.

const copy = {

 tagName: 'ul',

 attributes: { class: 'list' },

 children: [

   {

     tagName: 'li',

     attributes: { class: 'list_item' },

     textContent: 'Первый элемент списка',

   },

   {

     tagName: 'li',

     attributes: { class: 'list_item' },

     textContent: 'Второй элемент списка',

   },

 ],

}

This copy is used to create a “diff” between the original VDOM (list) and its updated version. Diff might look like this:

const diffs = [

 {

   newNode: {

     /* новая версия первого элемента списка */

   },

   oldNode: {

     /* оригинальная версия первого элемента списка */

   },

   index: {

     /* индекс элемента в родительском списке */

   },

 },

 {

   newNode: {

     /* второй элемент списка */

   },

   index: {

     /* ... */

   },

 },

]

The diff contains instructions for updating the RDOM. After identifying all the differences, we can send them to the DOM to perform the necessary updates.

For example, we can iterate over all the differences and either add a new child or update the existing one depending on the difference:

const domElement = document.quesrySelector('list')

diffs.forEach((diff) => {

 const newElement = document.createElement(diff.newNode.tagName)

 /* Добавляем атрибуты ... */

 if (diff.oldNode) {

   // Если имеется старая версия, заменяем ее новой

   domElement.replaceChild(diff.newNode, diff.oldNode)

 } else {

   // Если старая версия отсутствует, создаем новый узел

   domElement.append(diff.newNode)

 }

})

Please note that this is a very simplified version of how the VDOM might work.

VDOM and frameworks

Usually, we deal with VDOM when using frameworks.

The VDOM concept is used by frameworks such as React and Vue to improve the performance of DOM updates. For example, with React, our component list can be implemented like this:

import React from 'react'

import ReactDOM from 'react-dom'

const list = React.createElement(

 'ul',

 { className: 'list' },

 React.createElement('li', { className: 'list_item' }, 'Элемент списка')

)

// для создания элементов в React обычно используется специальный синтаксис под названием «JSX»

// const list = <ul className="list"><li className="list_item">Элемент списка</li></ul>
ReactDOM.render(list, document.body)

To update the list, just create a new template and transfer it again ReactDOM.render():

const newList = React.createElement(

 'ul',

 { className: 'list' },

 React.createElement(

   'li',

   { className: 'list_item' },

   'Первый элемент списка'

 ),

 React.createElement('li', { className: 'list_item' }, 'Второй элемент списка')

)

const timerId = setTimeout(() => {

 ReactDOM.render(newList, document.body)

 clearTimeout(timerId)

}, 5000)

Since React uses VDOM, even though we re-render the entire list, only the parts that actually changed are updated.

Conclusion

VDOM definitely deserves our attention. It provides a great way to decouple application logic from DOM elements, reducing the likelihood of inadvertently creating bottlenecks associated with DOM manipulation. Other libraries use the same approach in one way or another, and we are seeing this concept emerge as the preferred strategy for developing web applications.

Approach used Angular, which is the framework that made single page applications (SPA) so widely known, is called Dirty Model Checking (dirty model checking). It should be noted that DMC and VDOM are not mutually exclusive. An MVC framework may well use both approaches. In the case of React, this doesn’t make much sense, since React is, after all, just a library for the view layer.


Cloud VDS from Macleod fast and safe.

Register using the link above or by clicking on the banner and get a 10% discount for the first month of renting a server of any configuration!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *