React: Another Level of Indirection
Sign up for weekly Clojure tips, software design, and a Clojure coding challenge.
Any problem in computer science can be solved with another level of indirection. — David Wheeler
The React library from Facebook makes DOM programming functional by using a Virtual DOM. The Virtual DOM is an indirection mechanism that solves the difficult problem of DOM programming: how to deal with incremental changes to a stateful tree structure. By abstracting away the statefulness, the Virtual DOM turns the real DOM into an immediate mode GUI, which is perfect for functional programming. Further, the Virtual DOM provides the last piece of the Web Frontend Puzzle for ClojureScript.
David Nolen has written up a short explanation of how the Virtual DOM works, as well as some amazing performance benchmarking of React in a ClojureScript context. His work is important, so you should read it. I'd like to focus a bit more on the expressivity and why I would use React even if it were not so fast.
One can view MVC frameworks as an attempt to impose some structure on the code that has to interface with the DOM. The bargain they propose is this: wrap the DOM in View objects, so that subtrees of DOM nodes are managed by a View object. Wrap your state in Model objects, which will notify the View objects of changes. You thereby keep a layer of indirection between Models and Views, which inherently need different structure and need to change independently.
The layer of indirection (usually an event or observer system) solves a coupling problem. It decouples your state from the DOM, while leaving you to deal with all of the difficulties of the DOM. You are essentially making a new type of DOM that is better suited to your GUI domain than plain HTML, but is still a PITA. It is little more than a coat of paint. The DOM is still stateful.
React takes a different approach. It provides a level of indirection which solves the actual problem with the DOM—statefulness. The DOM has become a smart canvas. Paint the whole picture again, but only the different parts get wet.
Instead of wrapping the DOM in View objects, you create a Virtual DOM (completely managed by React) which mirrors the real DOM. When the model changes, you generate a new Virtual DOM. The differences are calculated and converted into batch operations to the real DOM. In essence, React is a function which takes two DOMs and generates a list of DOM operations, i.e., it's referentially transparent.
It's easy to imagine how this changes the game. You no longer need an initializer to set up the DOM and observers to modify it. The first Virtual DOM rendering is like the second one, in fact like any other! Your "View", if you want to call it that, is simply a function from state to Virtual DOM nodes. If the state and DOM nodes are immutable, all the better. There's less work to know if it has changed.
This also means that your Views can be composed functionally. Define a component (as a function) and your subcomponents, and build them up functionally. All of the functional abstraction and refactoring that you're used to is available. If you're doing it right, your code should get shorter, easier to read, and more fun to maintain.
My (short) experience rewriting a program to use React converted me. It was the only library I have used that actually made DOM programming fun and functional—and dare I say my code now works!
Which gets me to my last point, which is that React is the final puzzle piece for ClojureScript web frontend development.
- Problem: Global state management
- Solution: Atoms and persistent data structures
- Problem: Client-server communication
- Solution: EDN (also solved pretty well by JSON)
- Problem: Callback hell
- Solution: core.async
- Problem: Stateful DOM
- Solution: React
Any other problems left? I can't think of any. That's something to discuss on Twitter.
I suggest you try React. Om by David
Nolen is the most mature React ClojureScript library I know of. It does
a bit more than I've described here (Om manages your state tree for
you, among other things) and is evolving quickly. I have some code that
generates React Virtual DOM using
hiccup style with macros (so the
work is done at compile time), but other than that, contains only
half-baked implementations of what's in Om. If you're interested in
learning Om, I recommend my LispCast Single Page Applications with
Om. It's an
interactive course designed to guide you through building an Om app grom
the ground up. It's the fastest and most effective way to learn to
build Om app.