Quick Thoughts on React (virtual DOM)
I was thinking about how I’d answer one of our interview questions: “What are the benefits of React?” I feel like most of the usual answers here are not relevant.
- “React allows rendering to be pure functions” — true, but is this really a benefit? The main benefit we get from using pure functions in code is the ability to compose functions with confidence. Function composition largely isn’t the issue in rendering DOM. Render functions accept state and return DOM, some they can’t be composed per se. One function might call another to render another piece of DOM at a particular place in the tree, not to perform significant transformations on the DOM. And the render functions are essentially one step away from decidedly non-pure effects, the mutation of the DOM. So purity isn’t really much of a “benefit” (though perhaps the ability to inspect virtual-DOM output might help a little with debugging).
- Improved state management — this has little to do with React per se. The abundance of “React frameworks” show just how easy it is to make a mess of state management, because they all use the exact same underlying React, but differ in their handling of state. What is improved is that you don’t have to explicitly deal with the DOM as state, e.g. using jQuery to query the current DOM and transform it, and instead directly expressing the functional relationship between the current state and the DOM. But the nature of React has, arguably, led to an even bigger mess for application state management.
- Performance — using React virtual-DOM differencing is definitely faster than re-rendering the whole DOM. It’s probably often faster than the jQuery-like approach of having to first search for the DOM elements you want to mutate, since virtual DOM lets you do a sort of implicit bookkeeping of the relationship of various bits of markup in the tree. But React doesn’t come close in performance to a direct reactive binding approach like fReactive.
If we can winnow out one benefit of React, it would be the ability to express a direct functional relationship between state and DOM, as opposed to worrying about how changes in state lead to changes in DOM. Render functions are passed updated values of state, spit out a new chunk of virtual DOM, and React (heuristically) figures out what bits of the actual DOM need to be updated. Basically, React is doing the “numerical derivative” of the render function. Previous approaches a la jQuery were really attempts at directly coding the “analytical” derivative of the render function, directly mapping state changes to DOM changes, which is clearly going to be nightmarishly complex for anything but the simplest state->DOM mappings.
But virtual-DOM diffing is not the only approach, and arguably not even the best one. React (and all virtual DOM frameworks) use heuristics to approximate the diff, because doing a full exact difference between two trees is really expensive (O(n³), where n is the number of elements in the tree). React’s heuristics run in O(n). Direct reactive binding, however, is effectively O(1) (-ish, really scales as the number of binding changes). Consider the approach taken by fReactive. Looking at render functions for something like om-next compared to fReactive, there is little difference, mainly the explicit dereferencing of state variables in fReactive (that difference gets even smaller when compared with Reagent). The implementation, however, is vastly different. The “render function” for fReactive is called once to establish bindings between those explicitly deref’ed variables and DOM elements and attributes. Thereafter, changes to the variables propagate directly to changes in the DOM, no middle-man virtual DOM manipulation required.
So why has virtual DOM and all of the diff complexity won the day? Part of the answer may be the implied architecture. The virtual DOM approach, for whatever reason, seems to have inspired some reasonable architectural approaches for managing the complexity of application state updates. Reactive approaches seem to be viral, encouraging reactive programming everywhere. That was certainly the case with fReactive, leading to a complection of different concerns and code that was extremely hard to debug, once you moved past very basic applications. You could, of course, meld the state management approaches inspired by virtual DOM with the direct binding DOM update, perhaps worth thinking about. It maybe just hasn’t occurred because for whatever reason the virtual DOM approach puts you in a different head-space than reactive when thinking about state. But it technically seems feasible.
But all of this skates around the real issue: the DOM sucks for applications. Markup languages like HTML are great for making documents, where the tree structure of the language maps directly the the semantic and visual structure of the document. And while pieces of application may be effectively rendered in document form, as markup, certainly the larger structure of application UI is not well-represented by markup. Think about something as simple as a modal dialog: the content is probably nicely rendered via markup. But where the hell does the dialog “element” go within the global HTML tree? And what does it’s position in that tree “mean”? It certainly means nothing visually, but perhaps somehow pollutes how events are propagated, etc. The whole thing is gross. HTML was originally intended as a way of rendering scientific papers online. Its use for applications is simply due to the ubuiquity of web browsers as an interface. Attempts to improve or replace HTML, such as XAML, have been abject failures, not because HTML was superior, but because the epic suckitude of markup for applications becomes all that more obvious when having to learn a new tech from scratch. Everybody is inured to the warts of HTML. Tools like React which ease some of that pain are thus viewed as “solutions”.
Perhaps it’s time to examine the problem from the other direction. Rather than starting with HTML and trying to figure out how to minimize the resulting headaches, we should independently consider how best to represent application UI. Particular attention should be given to the problem of transforming application state changes into UI state changes, minimizing the complexity of calculating the “derivative” of the render function. That leads us to consider how best to represent the relationships between visual elements. HTML largely tries to express those relationships as hierarchy and ordering, but a more explicit and mathematical model will likely simplify how state transforms to UI. From these requirements, we can always work out how to transform this more natural representation to the DOM.