Towards the end of that post, I touched on how each of these frameworks' mechanisms for reactivity work under-the-hood through external links.
One of those mechanisms used by frameworks like React and Vue is called the "Virtual DOM" (Also known as the "VDOM") and use a process called "Reconciliation" to reflect the changes made to this "VDOM" to the real DOM.
Let's take a look at how this works in practical terms.
What is the Virtual DOM (VDOM)?
In a broad stroke, the virtual DOM (VDOM) is a reflection of the code you've written in your framework that eventually gets mirrored to the DOM. This is in order to make sure that updates to your JavaScript state are duplicated into the DOM via reactivity.
Too terminology heavy? No problem, here's an example.
Let's say that you have a bit of HTML:
<ul>
<li><p>One</p></li>
<li><p>One</p></li>
<li><p>One</p></li>
</ul>
This might create a DOM tree that looks similar to the following:
If you need a refresher on how the DOM works, check out our post on the topic.
Similarly, if you write the following JSX:
const App = () => {
return (
<ul>
<li><p>One</p></li>
<li><p>One</p></li>
<li><p>One</p></li>
</ul>
)
}
You'll end up with a VDOM that mirrors the markup you've written in JSX. This JSX is then reflected to the DOM itself:
The process of how these changes are mirrored is called "Reconciliation".
What is "Reconciliation"?
Reconciliation is the process of reflecting changes from a frameworks' virtual DOM into the DOM via a three-step process:
1) Listening for changes to the state
2) Diffing the changes made to the VDOM
3) Committing the changes from the VDOM to the DOM
What is the key
property?
While this process of reconciliation might seem simple at first, it can get quite complex. For example, consider how a list might be handled:
import { useState } from 'react';
const fakeNames = [
'Gulgowski',
'Johnston',
'Nader',
'Flatley',
'Lemke',
'Stokes',
'Simonis',
'Little',
'Baumbach',
'Spinka',
];
let id = 0;
function createPerson() {
return {
id: ++id,
name: fakeNames[Math.floor(Math.random() * fakeNames.length)],
};
}
export default function App() {
const [list, setList] = useState([
createPerson(),
createPerson(),
createPerson(),
]);
function addPersonToList() {
const newList = [...list];
// Insert new friend at random location
newList.splice(
Math.floor(Math.random() * newList.length),
0,
createPerson()
);
setList(newList);
}
return (
<div>
<h1>My friends</h1>
<button onClick={addPersonToList}>Add friend</button>
<ul>
{list.map((person) => (
<li>
<label>
<div>{person.name} notes</div>
<input />
</label>
</li>
))}
</ul>
</div>
);
}
Here, we're storing a list of our friends and allowing the user to add to this list by pressing a button. We even have a little place to store notes about your friends!
But notice what happens to those notes when someone gets added to the start of the list:
https://unicorn-utterances.com/content/blog/what-is-reconciliation-and-the-vdom/incorrect-user.mp4
See how the note about Little is now assigned to the wrong person.
This is because React needs some way to identify which element is which in a list. By default, this is the index of the list item - which is why our note about Little (whom was originally at the top of the list) is still at the top of the list when someone else is added, despite that not being correct.
Without this default behavior, the input
s inside of the list would all disappear every time the user added to the list, as it wouldn't know to avoid re-rendering the contents of the DOM on existing items:
To fix this, we just need to explicitly tell React which user is which in the list using a special key
property:
<ul>
{list.map((person) => (
<li key={person.id}>
<label>
<div>{person.name} notes</div>
<input />
</label>
</li>
))}
</ul>
Conclusion
In this series, we've explored how React handles reactivity and how it uses the Virtual DOM and reconciliation to make reactivity work.
These two combined build the foundation for how React works behind the scenes. In the next few posts, starting with "What is server-side rendering (SSR) and static site generation (SSG)", we'll explore how React has gradually evolved past the typical client-side rendering process and into the server.
Eventually, this series will show you how to use React Server Actions in-depth and how you can utilize your React knowledge to become a full-stack developer.
Sound like fun? We hope so! Join us in our Discord and let us know what other topics you'd like to see on the site.
Top comments (3)
Appreciate the images and code for really breaking this down!
Thanks for the visuals and the code that truly help to dissect this!
As you explained in your first post, it can be tricky to keep track of all state changes, so the Idea behind the VDom was to automate this. Instead of handling each event, you just rebuild the complete DOM and then check if something has changed. Only those changes need to be reflected to the real dom. You mentioned, that this can get complex, but there are some practical aspects that should be mentioned too.
Reconciliation is a quite labourious task. You need to check the whole DOM even if only a single letter was changes. And a calculation might cause numerous state changes in a single call. So, you need to care that the process is not called too often (some kind of "debouncing"). I suppose it is not easy to find the right balance between updating the DOM too often and slowing down the reactivity.