There’s some confusion and misunderstanding over what benefits the use of a Virtual DOM in React.js provides.
We’ve all heard that updating the Document Object Model (DOM) directly is inefficient and slow. However, few of us actually have the data to back it up. The buzz about React’s Virtual DOM is that it is a more efficient way to update the view in a web application, but very few of us know exactly why and whether this efficiency will result in faster page render times.
Putting aside the other benefits of using React, such as one-way data binding and components, I will discuss what exactly the Virtual DOM is, and whether it alone justifies the use of React over other UI libraries (or no UI library at all).
Two of the most important ideas of reactive programming are that systems should be event-driven and responsive to state changes.
The DOM’s user interface components have internal state, and updating the browser isn’t as simple as just regenerating the DOM whenever something changes. If Gmail did this, for example, you would be constantly annoyed when the entire browser window refreshed in order to display a new message, wiping out the email you were composing.
The statefulness of the DOM is why we need user interface libraries and solutions such as key/value observation (which is used by Ember among others) or dirty checking (which is used by Angular). UI libraries handle watching for changes to the data model and updating the correct part of the DOM when these changes occur, or watching for changes in the DOM and updating the data model when they occur.
This type of watching and updating is called two-way binding, and it can often make working with user interfaces very complex and confusing.
What makes React and its Virtual DOM so different is that it’s simpler than other approaches to making JavaScript reactive from a programmer’s perspective. You write pure JavaScript that updates React components, and React updates the DOM for you. The data binding isn’t intertwined with the application.
React uses one-way data binding to make things simpler. Every time you type in an input field in a React UI, for example, it doesn’t directly change the state of that component. Instead, it updates the data model, which causes the UI to be updated and the text you typed into the field appears in the field.
Every talk and article about the Virtual DOM will point out that although today’s JavaScript engines are extremely fast, reading from and writing to the browser’s DOM is slow.
This isn’t exactly true. The DOM is fast. Adding and removing DOM nodes doesn’t take much more than setting a property on a JavaScript object. It’s a simple operation.
What is slow, however, is the layout that browsers have to do whenever the DOM changes. Every time the DOM changes, browser need to recalculate the CSS, do layout, and repaint the web page. This is what takes time.
Browser makers are continually working to shorten the time it takes to repaint the screen. The biggest thing that can be done is to minimize and batch the DOM changes that make redraws necessary.
This strategy of reducing and batching DOM changes, taken to another level of abstraction, is the idea behind React’s Virtual DOM.
Like the actual DOM, the Virtual DOM is a node tree that lists elements and their attributes and content as objects and properties. React’s render() method creates a node tree from React components and updates this tree in response to mutations in the data model, caused by actions.
Each time the underlying data changes in a React app, a new Virtual DOM representation of the user interface is created
This is where things get interesting. Updating the browser’s DOM is a three-step process in React.
One would think that re-rendering the entire Virtual DOM every time there’s a possibility that something has changed would be wasteful — not to mention the fact that at any one time, React is keeping two Virtual DOM trees in memory.
But, the truth is that rendering the Virtual DOM will always be faster than rendering a UI in the actual browser DOM. It doesn’t matter what browser you’re using: this is just a fact. But it’s also sort of irrelevant.
The problem is that your users can’t see the Virtual DOM. It’s like owning 10,000 tacos in another county. Sooner or later, you need have the tacos delivered, and that can be expensive and slow.
Sticking with the taco analogy (because, why not?), is it faster to have all of the tacos shipped to you at once, or to calculate the difference between how many tacos you need and how many tacos you own and just ship bare minimum number of tacos? It’s certainly less expensive to ship 4 tacos when you only want 4 tacos.
The next question is: how do you go about ordering the tacos? Do you say, “Send me 4 tacos” or do you say, “Here’s how I’d like my taco situation to look. You work out the details.”
The 2nd approach is how the Virtual DOM works. You write the code to make your UI look how you want it to look, and the Virtual DOM works out the difference between that and how it looks now and only updates what needs to be updated.
React does this magic by attaching attributes to elements in your document and manipulating them individually (using these very specific ID attributes) after doing the diff to determine what needs updating. The Virtual DOM inserts additional steps into the process, but it creates an elegant way to do minimal updates to the browser window without you having to worry about the actual methods being used or even what needs to be updated and when.
I’m not going to do benchmarking tests. A number of other people have created different tests to figure out whether React’s Virtual DOM approach is faster. The most frequent conclusion seems to be that it isn’t, unless it is, but it doesn’t really matter because the tests aren’t realistic.
The Virtual DOM adds a layer of scripting on top of whatever optimizations the browser is already making in order to make these minimized DOM manipulations transparent to the developer. This additional layer of abstraction makes React much more CPU-intensive than other methods of updating the DOM.
Here’s a “Hello, world!” example that uses native JavaScript DOM manipulation:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello JavaScript!</title> </head> <body> <div id="example"></div> <script> document.getElementById("example").innerHTML = "<h1>Hello, world!</h1>"; </script> </body> </html>
And here’s how you would do the same thing in React. Note that we need to include React, React DOM, and babel, which is responsible for converting the XML-looking code in the render() method into vanilla JavaScript.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello React!</title> <script src="build/react.js"></script> <script src="build/react-dom.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> </head> <body> <div id="example"></div> <script type="text/babel"> ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') ); </script> </body> </html>
The native approach is always going to be faster. And just for kicks, let’s look at the proof.
Here’s the timeline for loading and rendering the direct DOM manipulation “Hello, World!” page (in Chrome).
And here’s the timeline for loading and displaying the React “Hello, world!” app in the same browser.
Notice that everything is essentially the same, except for time it takes to do the scripting. React is slower than using DOM methods directly, and by a lot! But, how does it compare to jQuery?
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello jQuery!</title> <script type="text/javascript" src="scripts/vendor/jquery-1.12.3.min.js"></script> </head> <body> <div id="example"></div> <script> $(document).ready(function(){ $("#example").html("<h1>Hello, world!</h1>"); }); </script> </body> </html>
jQuery’s total time to display the simplest Hello, world application is about 50ms slower than pure JavaScript, and both are about 3 times faster than React.
So, clearly, if it was just about how fast they are, pure JavaScript and jQuery win easily.
However, this is just common sense. Using a library is slower than not using a library. And creating an in-memory representation of the DOM before actually manipulating the DOM is slower than just manipulating the DOM directly, when it’s done correctly.
Now that we have the obvious out of the way, let’s discuss exactly how you can make using the Virtual DOM faster.
My “Hello, world!” examples are unfair to React because they only deal with the initial render of a page. React is designed for managing updates to pages.
Because of the Virtual DOM, each change to the data model can trigger a complete refresh of the virtual user interface. This is very different from systems used by other libraries, which observe aspects of the document and update them when necessary. The Virtual DOM often actually uses less memory than other systems, because it doesn’t need to hold observables in memory.
However, there are inefficiencies when you’re comparing two entire virtual DOMs each time an action occurs. The CPU requirements can be formidable for complex user interfaces.
For this reason, React developers can’t be completely passive when it comes to deciding what to render. If you know that a certain action won’t affect a certain component, you can tell React to not analyze that component looking for changes — saving significant resources and significantly speeding up your application. Demonstrations of React’s performance often include raw numbers showing high CPU usage followed by the dramatic improvements that can be realized with good development practices such as memoization.
The truth is, there may be no way to actually figure out if using Virtual DOM is faster than an approach that updates the DOM directly, because it depends on a million different factors, but mostly it depends on how well you optimize your application.
This isn’t surprising or revolutionary. Any tool is only as good as the person who’s using it. What React and the Virtual DOM give us, however, is a simpler way to think about updating the browser. This simplicity can free up significant mental resources and make optimizing the user interface easier. This is where the real benefits — both in performance and productivity — lie in React.
Author: Chris Minnick, one of Accelebrate's instructors.
Accelebrate offers a wide array of React training courses, including React Using JavaScript/ECMA Script, Redux for React using TypeScript, React Native, Advanced React and Redux, and more.
Written by Chris Minnick
Chris has authored and co-authored over a dozen books including titles in the For Dummies series, as well as several books teaching kids to code. Chris specializes in JavaScript technologies and writes and maintains courseware that’s used for training software developers at some of the largest companies in the world.
We offer private, customized training for 3 or more people at your site or online.
Our live, instructor-led lectures are far more effective than pre-recorded classes
If your team is not 100% satisfied with your training, we do what's necessary to make it right
Whether you are at home or in the office, we make learning interactive and engaging
We accept check, ACH/EFT, major credit cards, and most purchase orders
Alabama
Birmingham
Huntsville
Montgomery
Alaska
Anchorage
Arizona
Phoenix
Tucson
Arkansas
Fayetteville
Little Rock
California
Los Angeles
Oakland
Orange County
Sacramento
San Diego
San Francisco
San Jose
Colorado
Boulder
Colorado Springs
Denver
Connecticut
Hartford
DC
Washington
Florida
Fort Lauderdale
Jacksonville
Miami
Orlando
Tampa
Georgia
Atlanta
Augusta
Savannah
Hawaii
Honolulu
Idaho
Boise
Illinois
Chicago
Indiana
Indianapolis
Iowa
Cedar Rapids
Des Moines
Kansas
Wichita
Kentucky
Lexington
Louisville
Louisiana
New Orleans
Maine
Portland
Maryland
Annapolis
Baltimore
Frederick
Hagerstown
Massachusetts
Boston
Cambridge
Springfield
Michigan
Ann Arbor
Detroit
Grand Rapids
Minnesota
Minneapolis
Saint Paul
Mississippi
Jackson
Missouri
Kansas City
St. Louis
Nebraska
Lincoln
Omaha
Nevada
Las Vegas
Reno
New Jersey
Princeton
New Mexico
Albuquerque
New York
Albany
Buffalo
New York City
White Plains
North Carolina
Charlotte
Durham
Raleigh
Ohio
Akron
Canton
Cincinnati
Cleveland
Columbus
Dayton
Oklahoma
Oklahoma City
Tulsa
Oregon
Portland
Pennsylvania
Philadelphia
Pittsburgh
Rhode Island
Providence
South Carolina
Charleston
Columbia
Greenville
Tennessee
Knoxville
Memphis
Nashville
Texas
Austin
Dallas
El Paso
Houston
San Antonio
Utah
Salt Lake City
Virginia
Alexandria
Arlington
Norfolk
Richmond
Washington
Seattle
Tacoma
West Virginia
Charleston
Wisconsin
Madison
Milwaukee
Alberta
Calgary
Edmonton
British Columbia
Vancouver
Manitoba
Winnipeg
Nova Scotia
Halifax
Ontario
Ottawa
Toronto
Quebec
Montreal
Puerto Rico
San Juan