Managing UI complexity is hard. State exists on the server, in browser memory and in the DOM, all mutating over time. Keeping it in sync is easy at first, but as you dial up the interactivity things get buggy and fragile. Is there a simpler way? We'll look at building apps that minimize mutable state and embrace a simpler, functional data flow with Facebook's React.
"Reactive, Component-based UIs"?
First, let's talk about what this phrase means. Plainly: it's how I want to build user interfaces (UIs) on the web, embodying years of trial and error with different frameworks and approaches for building DOM-based UIs. But it's a little vague, so let's dive into some definitions for the sake of this article:
It's no secret that components are the future of the web. Whether it's to-the-spec web components or some similar abstraction, if your framework doesn't have a good component story, you're using the wrong one (although most of the big ones do).
A component abstraction is important for web applications in the same way abstraction is generally good for any kind of programming. It enables us to build sections of our UI:
- In isolation from other components (sections of the UI)
- In functional pieces, reusable anywhere else in the app
- In a composable way, to freely combine and nest our components to build larger ones (think: russian-doll style composition)
Reactive programming, specifically Functional Reactive Programming (FRP) is a hot topic right now, and somewhat of a loaded term. I am no expert in this paradigm, so in order to prevent confusion, we are going to limit the definition for our discussion today to include two of the major reactive properties as outlined in the Reactive Manifesto (which I highly recommend checking out yourself). The most important reactive properties for web UIs are:
1) Event-driven workflow
This may sound redundant, since the first web programming we ever did likely involved event-handlers, working with asynchronous APIs, etc. This is how we write programs on the web. That doesn't change much in this paradigm, and in fact we will lean on it even more to enable reactivity in our UI.
2) Responsive to state changes
The important thing to remember here is that our UI should update without having to do imperative DOM changes. This is important. Writing our UI layer using those techniques just does not work in the long run. The number of possible state combinations you can get into, and tracking which imperative updates need to happen in each case is wrought with bugs and unpredictable behavior. No more touching the DOM!
React: Facebook's Reactive UI Library
React is Facebook's attempt at packaging up a set of best practices for building reactive, component-based UIs. If you are new to React, I consider running through the following resources to get up to speed:
- Thinking in React: great for understanding what React is all about
- React: Rethinking Best Practices: Pete Hunt's talk from JSConf 2013
- Egghead.io Training Videos to learn by doing
- The Official Tutorial and Getting Started Guide
Done? Great. Now let's move on to the larger question: why should you care?
This is an excellent question. I am not trying to convince you to stop what you're doing and rewrite all your production applications in React right now.
We are here to discuss what makes React unique, which problems it solves well. It's not just another me too framework, it truly brings some new concepts to web programming, and knowing what those concepts are will make you a better developer, no matter what you end up using.
More importantly, as a seasoned front-end developer I think you will notice that React doesn't try to solve every problem. Just the really hard ones. And it solves them really well.
Without further ado, let's look at what makes React a great tool to add to your web development toolbelt.
Reactivity in the DOM
A few sections back, we discussed reactivity, and what it means to write programs in a reactive style. Remember that we decided to limit our definition of reactive for the sake of this article to systems that are event-driven and responsive to state changes.
We'll examine how React handles evented programming a little later, but for now suffice it to say that it works just like you'd expect: it feels just like native DOM Event programming. I want to discuss the second property of reactivity a bit further: responsiveness to state changes. But don't we already have a way to deal with these state problems?
Solution: Data Binding!
We fondly remember our first Backbone, Knockout, or insert-framework-here application, and how magical it felt to have the DOM update automatically when our models did. This is exactly the reactive feel we're seeking. But as our applications grew in complexity, things started to get harder to manage.
So what did we do when our data-bound UI logic started to get buggy and fragile? Time to double-down! Two-way data binding to the rescue!
Solution?!: Data Binding
This is not an anti-databinding rant. However, all that complexity on the right side of the diagram above?: no matter how magical your data-binding solution is, that complexity still exists. Your framework (Ember, Angular, etc.) is just throwing a curtain over it, saying "don't worry about it!" But what happens when we do need to worry about it? What happens when our events are flowing in all directions, and triggering at unpredictable times? What happens when we can no longer make sense of the timing and behavior of our system, and we have urgent bugs to fix?
This is not just a hypothetical example, either. The Angular core team does a commendable job of publishing their meeting notes and thoughts to the world. This issue of unpredictable data- and event-flow has been a recent topic:
Things weren't always this hard. How did we end up here?
The Simplicity of Server Rendering
Before the rise of the front-end frameworks, we didn't have these problems. Things were simpler. We never got into "weird states" in the UI render flow. Everything happened in predictable order:
- Client makes a request for a resource
- Web server receives request
- Application code takes snapshot of data at time of request, and merges with template
- Web server responds to client with accurate and complete view of application state
We never worried about these events happening in unpredictable order, or data changing halfway through the request, leaving part of the UI in an old and/or incompatible state. Events only flowed in one direction. We had reliability and predictability.
The above example uses Rails to demonstrate how a web page renders over HTTP. However, this could be anything, including PHP or static HTML pages.
So what's missing with this picture? Of course, the responsiveness and interactivity of modern front-end applications, which is undeniable. How can we bring that reliability and predictability of server rendering to the world of responsive front-end applications and SPAs?
React's Big Idea: Rerender Everything, All the Time
"Every time your data changes, it's like hitting refresh in a server rendered app." -- Pete Hunt, Facebook
With React, we get back to the simpler uni-directional flow that we had in the server rendering example. Imagine the DOM is completely "refreshed" anytime the data in our app changes, essentially a browser refresh without the extra HTTP requests and without a single line of imperative, buggy DOM manipulation code.
How does that work? How can we continually refresh the DOM without destroying our applications responsiveness and performance? Isn't manipulating the DOM slow and error-prone? Well, with React you don't manipulate the DOM directly. React uses a fast in-memory virtual DOM to record all your state changes, does a diff to calculate the minimal number of changes needed to bring your UI up to the latest state, and executes them for you. Remember: No more touching the DOM!
DOM Diffing: A Scalpel not a Sledgehammer
So how does React know what has changed, and thus, what to update? If you're familiar with Angular and how it does dirty checking, a similar approach is used here: anytime
setState is called within a component, it is considered dirty. On the next diff cycle, React will call the component's
render() method to determine how it should be rendered, compare that to what's actually in the DOM at the time, and reconcile any differences for you.
It's Like Source Control for the DOM
Source Control systems like Git are valuable because of their fine-grained diff detection and history tracking. As the example below shows, git detects not only which files changed in the project, but which individual lines in those files. And within those lines, the diff mechanism detects the exact characters that were altered.
React's diffing works similar to this. It doesn't just näively blow away the whole component and rerender when something changes, but it uses it's fine-grained diffing algorithm to isolate and operate on only the minimal set of changes that need to happen. So instead of reloading the entire
<ul> in the example above, it will do things like update the
class attribute of the first element from
gain, and then replace just the text nodes within it that have been changed.
If that does not impress you, you need to consider the impact from not only a raw performance perspective, but perhaps more importantly from a developer productivity and sanity perspective: no more keeping track of an exponential number of possible UI states, and figuring out which DOM manipulations need to happen for each one. Developers express in a declarative way what the UI should look like at any point in time, and React makes it so. I cannot express the importance of this latter point enough.
Sidenote: Smarter than React?
The diff mechanism described above works extremely well for the majority of cases, but (of course) there are times when you need to optimize. In React, this is dead simple: simply implement your own version of
shouldComponentUpdate to customize the diff algorithm for your application needs.
return true to indicate a rerender, or
return false if you want to skip it. It's as simple as that.
Understanding by Doing: Building a Stock Ticker Application
I can describe the diff mechanism and the amazing benefits it brings until I'm blue in the face, but it's best understood by getting our hands dirty and building something with it. For the next installment in this series, we'll look at the diff mechanism from a practical perspective, by building our own mini-application: a real-time stock ticker. We'll attach a Mutation Observer to monitor the number of changes our library makes to keep the DOM up to date, and compare that to the 'sledgehammer' approach needed to accomplish the same thing without React (example using Backbone).
- React I: Reactive, Component-based UIs
- React II: Building a Stock Ticker Application
- React III: Gateway to Functional Patterns