I’m sure that my humble blog isn’t the first site that you’ve visited whose topic was the React.js library. If it is then I’m obviously honoured but most likely I was not your first.
This being the case you’ve probably heard of the VirtualDOM. In fact if you’ve seen or heard anything about React.js then you have definitely been told that the VirtualDOM is part of why React is so amazing because it’s super fast….and stuff.
Of course, you being a developer of the highest calibre, didn’t just leave it at that you opened your favourite generic search engine and typed something along the lines of “Why is the VirtualDOM so fast anyway?” and now here you are reading this thinly veiled attempted at increased SEO.
Hopefully now I have your attention though, I can spend the next couple of hundred words giving you the 30k feet answer to the question you typed moments ago.
What is the VirtualDOM anyway?
The virtualDOM is a technology leveraged heavily in the React.js library which creates an in-memory version of the real DOM. From there our application ticks away and during operation events occur which may be as a result of user actions or from calls to servers. At this point through our render methods we are instructing react to update that in-memory representation of the DOM that was created. This update is super-fast!
Now that the change has occurred the in-memory DOM the changes that need to be made to the real DOM are ready to be optimized to ensure that the least number of changes are made to the real DOM, this also makes updating the DOM a lot quicker.
But then you’ve probably already read this description before and wondered, like I did, “Yeah but why? Why is the VirtualDOM fast and more importantly why do people keep saying the real DOM is so slow?! I didn’t even know it had a problem!”. It seems that we did have a problem but luckily it’s the opinion of React fans that the solution is already here.
What was wrong with my old DOM?
We have two unanswered questions at this point. Why is virtualDOM fast? and why is real DOM slow? I’ll begin with the latter. By all accounts, whilst researching for this article, I could find two main arguments as to why the real DOM was so “unbearably” slow.
The first can be boiled down more or less to the fact that the architecture of the technology we are using (the DOM) getting in the way of the way that we use the technology (reactively).
Let me explain, when your browser makes a request to the server for your page and the response is delivered with the HTML, the browser takes this and loads it into the DOM. It’s from this point onwards that your options for manipulating this HTML are limited to the interface provided by the DOM.
So, in order for us to attempt to ensure that we don’t have to be expected to keep track of all the interactions we will inevitably be having with the DOM and also make sure that they are being made in an efficient fashion React implements the virtualDOM. The optimisations that the VirtualDOM makes in the way we interact with the real DOM aren’t insignificant either because they optimise the use of the heaviest operation in the pipeline.
It’s called Smart-Diffing and all the hip kids are doing it!
So now that we’ve established the problems caused by the dysphoria between the applications that we want to write using the DOM and the reality of the way that the DOM itself is implemented, let’s explore why the VirtualDOM has come to our collective rescue.
As we established earlier the VirtualDOM achieves it’s excellent results through two means; first it is able to determine the differences that the events within the system have caused very quickly and this is provided through an algorithm called smart-diff. Second, once the VirtualDOM has established what’s changed it’s able to make sure that we make changes to the real DOM in such a way that we cause as few repaints as possible.
Rather than writing a short novel walking you through all the key features of why Smart-diffing is just so quick I’d like to explain to you the element of the algorithm that clicked for me the quickest as to how the optimisation worked so well and particularly why this made such a difference in the realm of react. Hopefully this will give you a flavour of the funky things happening in the algorithm and inspire you to find out more for yourself. Without further ado…
Which one of these things ain’t like the others?
We’ve talked about how when we do a repaint in the real DOM or when we update the virtualDOM we need to find the elements that have changed. Take a moment to think about how you might implement something that could work out what has changed in a tree of DOM nodes for yourself.
You might compare the content of the tag and see if that’s changed or perhaps you might check the attributes of the element. Both of these options would do a fine job at finding differences; but by far the most efficient way to to discover if something has changed within an element would be to determine if the tag type has changed right? Consider the code snippet below:
|<h1>Change is scary!</h1>|
|<h2>Be the change you want to see in the world</h2>|
Now imagine if the first
div represented the DOM before we made changes the second
div was what was there after we made our change. Programmatically the best way to determine that something has change here is to first to compare the tags (and in reality their attributes), then if we can’t determine if anything has changed then check the contents.
Because the tag has changed from a
h1 to a
h2 we can quickly and easily determine that something has changed. More so than if, for instance, a
div had changed. There you go. Did the penny drop? Yep, modern web pages are comprised of a metric flip-tonne of more or less identical
divs. So you can see how trying to make this efficient to diff is a problem.
Enter React. If you read my article on JSX then you’ll know that one of the funky things that React does with JSX is make our React components usuable as tags in JSX and obviously the VirtualDOM knows about these tags and what they represent.
So whilst it is doing it’s diffing rather than taking on the task of sifting through endless amounts of
divs the VirtualDOM has the added advantage of being able to compare entire react components against each other to see if they’ve changed which can effectively cut out the need to check a heap of virtually identical elements. For example a
<ShowSomething /> element is easier to tell apart from a
<ShowNothing /> element than a telling the difference between
<div style="display:block">I'm the same</div> and
<div style="display:none">I'm the same</div>.
Once again, this is far from the whole story on how Smart-diffing works it was just a part that I thought was clever and was a part that directly leverages the way react works to gain the benefits it does.
Round ’em up and repaint the lot of ’em!
Having worked out the the elements that have changed as a result of an event the VirtualDOM is now read to work out what it needs to tell the real DOM about all this hoo-ha that’s just happened. It does this through a process called subtree rendering.
What this essentially boils down to is that react makes some fairly safe assumptions that if you’ve just changed a node that there’s a reasonable chance that the children of that node have probably, even superficially, changed in some way. Safe in this knowledge the VirtualDOM just gives up on that branch of the DOM and decides that we might as well update all of them.
This might sound like a step backward with regards to performance but when you think about doing things this way should perform better the lower the down the DOM tree the changes happen. So our worries are reduced in that respect because the less children a node has the more chance of a larger portion of it’s of children are going to need updating.
Furthermore even as we move further up the tree when think about it there is probably more of a chance that a proportionally larger number of children have changed as a result of something in this higher up node changing. Yes, in some cases we are still grabbing nodes that may not have needed repainting, but in most cases this is going to fall within the bounds of the 80/20 rule.
What we are trading off here is these unnecessary repaints and in return we reprise the extra cycles that it would’ve taken us to diff the rest of the children in below that node.
As an aside, given what we’ve just talked about, whereby React assumes that any children under an updated node are assumed as candidates for a repaint, you can see why that it is not advisable to update the root element of your react applications regularly!
But even if we do get ourselves in a situation where we find that all these extra repaints are happening regularly enough toaffect our performance we do have in our arsenal a technique called “Selective Subtree Rendering”. This is where we can use react’s
shouldComponentUpdate hook to instruct react to only repaint if a defined criteria are met. If
shouldComponentUpdate returns false then this component’s node in the VirtualDOM tree and all it’s children are ignored for repainting purposes.
Every end is the start of a new beginning
During my research for this post I spent a lot of time reading literature and watching videos about the VirtualDOM. As a parting gift until I do get around to writing more about the VirtualDOM I thought I’d share with you my favourite links out of the ones I found in my search. Enjoy!