Why is React's concept of Virtual DOM said to be more performant than dirty model checking?
I saw a React dev talk at (Pete Hunt: React: Rethinking best practices -- JSConf EU 2013) and the speaker mentioned that dirty-checking of the model can be slow. But isn't calculating the diff between virtual DOMs actually even less performant since the virtual DOM, in most of the cases, should be bigger than model?
I really like the potential power of the Virtual DOM (especially server-side rendering) but I would like to know all the pros and cons.
Solution 1:
I'm the primary author of a virtual-dom module, so I might be able to answer your questions. There are in fact 2 problems that need to be solved here
- When do I re-render? Answer: When I observe that the data is dirty.
- How do I re-render efficiently? Answer: Using a virtual DOM to generate a real DOM patch
In React, each of your components have a state. This state is like an observable you might find in knockout or other MVVM style libraries. Essentially, React knows when to re-render the scene because it is able to observe when this data changes. Dirty checking is slower than observables because you must poll the data at a regular interval and check all of the values in the data structure recursively. By comparison, setting a value on the state will signal to a listener that some state has changed, so React can simply listen for change events on the state and queue up re-rendering.
The virtual DOM is used for efficient re-rendering of the DOM. This isn't really related to dirty checking your data. You could re-render using a virtual DOM with or without dirty checking. You're right in that there is some overhead in computing the diff between two virtual trees, but the virtual DOM diff is about understanding what needs updating in the DOM and not whether or not your data has changed. In fact, the diff algorithm is a dirty checker itself but it is used to see if the DOM is dirty instead.
We aim to re-render the virtual tree only when the state changes. So using an observable to check if the state has changed is an efficient way to prevent unnecessary re-renders, which would cause lots of unnecessary tree diffs. If nothing has changed, we do nothing.
A virtual DOM is nice because it lets us write our code as if we were re-rendering the entire scene. Behind the scenes we want to compute a patch operation that updates the DOM to look how we expect. So while the virtual DOM diff/patch algorithm is probably not the optimal solution, it gives us a very nice way to express our applications. We just declare exactly what we want and React/virtual-dom will work out how to make your scene look like this. We don't have to do manual DOM manipulation or get confused about previous DOM state. We don't have to re-render the entire scene either, which could be much less efficient than patching it.
Solution 2:
I recently read a detailed article about React's diff algorithm here: http://calendar.perfplanet.com/2013/diff/. From what I understand, what makes React fast is:
- Batched DOM read/write operations.
- Efficient update of sub-tree only.
Compared to dirty-check, the key differences IMO are:
Model dirty-checking: React component is explicitly set as dirty whenever
setState
is called, so there's no comparison (of the data) needed here. For dirty-checking, the comparison (of the models) always happen each digest loop.DOM updating: DOM operations are very expensive because modifying the DOM will also apply and calculate CSS styles, layouts. The saved time from unnecessary DOM modification can be longer than the time spent diffing the virtual DOM.
The second point is even more important for non-trivial models such as one with huge amount of fields or large list. One field change of complex model will result in only the operations needed for DOM elements involving that field, instead of the whole view/template.
Solution 3:
I really like the potential power of the Virtual DOM (especially server-side rendering) but I would like to know all the pros and cons.
-- OP
React is not the only DOM manipulation library. I encourage you to understand the alternatives by reading this article from Auth0 that includes detailed explanation and benchmarks. I'll highlight here their pros and cons, as you asked:
React.js' Virtual DOM
PROS
- Fast and efficient "diffing" algorithm
- Multiple frontends (JSX, hyperscript)
- Lightweight enough to run on mobile devices
- Lots of traction and mindshare
- Can be used without React (i.e. as an independent engine)
CONS
- Full in-memory copy of the DOM (higher memory use)
- No differentiation between static and dynamic elements
Ember.js' Glimmer
PROS
- Fast and efficient diffing algorithm
- Differentiation between static and dynamic elements
- 100% compatible with Ember's API (you get the benefits without major updates to your existing code)
- Lightweight in-memory representation of the DOM
CONS
- Meant to be used only in Ember
- Only one frontend available
Incremental DOM
PROS
- Reduced memory usage
- Simple API
- Easily integrates with many frontends and frameworks (meant as a template engine backend from the beginning)
CONS
- Not as fast as other libraries (this is arguable, see the benchmarks below)
- Less mindshare and community use