Build your own Virtual DOM
Problem Statement:β
Often times front end developers are faced with bugs due to multiple re-rendering of the DOM and other component lifecycle changes. This is because they may not fully understand how the Virtual DOM works and when it actually applies changes to the DOM tree in the browser. As a result, the code they write may not be very stable or maintainable and may contain unnecessary re-renders, which can bloat the code and reduce the performance of the application.
Performing this exercise will be helpful to front-end developers because it will provide a hands-on opportunity to understand and implement one of the core concepts used by modern web frameworks to optimize web application performance. Building your own Virtual DOM library will help you to gain a deeper understanding of how Virtual DOM works and how to leverage its benefits to improve web application performance. By completing this exercise, you will be better equipped to make informed decisions about which libraries and frameworks to use for your own projects, and to develop more efficient, performant web applications.
Overview:β
Virtual Document Object Model (DOM) is a concept used by modern web frameworks such as React, Vue, and Angular to optimize web application performance. By using a Virtual DOM, changes in the application state are first reflected in the Virtual DOM, which then updates the actual DOM. This way, the framework only updates the necessary parts of the DOM, reducing the number of expensive DOM manipulations required.
Here are general steps of how a virtual DOM functions:
- When a user interacts with a web application, the virtual DOM is updated with the new state of the application.
- The virtual DOM then compares the updated state with the previous state to determine what has changed.
- Once the changes have been identified, the virtual DOM creates a minimal set of instructions (known as a "diff") that are required to update the real DOM.
- Finally, the real DOM is updated with the changes, and the web page is re-rendered to reflect the new state of the application.
Implementation Details:β
Virtual DOM Element Objectβ
The DOM is usually represented as a tree in the browser, as you can see in the image down below:
Each node in the tree represents an HTML element, these elements can have branches that have child elements, Each node apart from containing their children has certain other properties which are known as attributes, these attributes could be things like href, source image (src) etc. The HTML in the browser is then rendered top to bottom.
This tree can be represented as an object in Javascript, here is an example object representation of a simple virtual DOM Object.
<div id="main">
<div id="header">
<h1 id="heading">
Hello World
</h1>
</div>
</div>
{
"tag" : "div",
"attributes" : {
"id" : "main"
},
"children": [
{
"tag" : "div",
"attributes" : {
"id" : "header"
},
"children" : [
{
"tag" : "h1",
"attributes" : {
"id" : "heading"
},
"children" : "Hello World"
}
]
},
]
}
Diffing Algorithm:β
Whenever something updates or chanes in the virtual DOM, a copy of the virtual DOM is made with the changes done on it, then the original and the modified virtual DOM objects are compared, and the differences are then reflected on the actual DOM. React for example keeps tracks of the changes and when the proper time comes it performs a batch update.
You will also need to program a "diff" algorithm, which runs whenever the state or the elements in the virtual DOM updates. This algorithm will only update components that are modifed in the virtual DOM. This algorithm is responsible for making our virtual DOM highly performant as compared to acutal DOM. You can read more about the diffing algorithm by going to the links in the resources section.
There are certain things that you need to keep in mind, firstly if the root of a tree is changed, e.g. a div element is converted to a span than the root and all its children will be recreated, however if only a specific attribute changes than only that will be updated and nothing will be recreated.
Exercise Details:β
In this exercise you should implement a Virtual DOM library using JavaScript or TypeScript. The library should have the following functionality:
- Create virtual nodes with a given tag name and attributes.
- Create a virtual text node with a given text content.
- Append a child virtual node to a parent virtual node.
- Remove a child virtual node from a parent virtual node.
- Update the attributes or text content of a virtual node.
- Diff two virtual nodes and return a list of patches to be applied to the actual DOM.
To complete the exercise, you should create a simple web page that uses your Virtual DOM library to render dynamic content. This web page should contain:
- A simple counter that updates on button clicks.
- An input box and a submit button which changes the text context of header
- A dynamic unordered list which you can add or remove elements by clicking the plus or minus buttons.
Your Virtual DOM library should be designed to handle changes to the application state efficiently, updating only the necessary parts of the DOM to improve performance.
Considerations:β
- You should make sure that your implementation does not use mutliple O(N2) time complexity functions when comparing the difference between virtual DOM and the actual DOM. Your implementation should be actually faster than updating the actual DOM. Read more about the heuristic approach that React has adopted to further optimize your code (Ideally, you should compare your virtual dom updates to actual DOM updates using console.time function).
- Make sure it easy to distinguish variables and code that performs actions on virtual DOM from the code that functions on the actual DOM.
- Have unique identifiers for your list or dynamic lists otherwise you will have unecessary computations
Further Learning:β
- Build your own JSX transpiler for your custom virtual DOM library.
- Make a global state management system for your custom virtual DOM library.
- Create a function for running code logic whenever a component is mounted (similar to use useEffect or componentdidMount)