A place to cache linked articles (think custom and personal wayback machine)
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

index.md 19KB

title: Combining React, Flux & Web Components url: http://futurice.com/blog/combining-react-flux-and-web-components hash_url: b444545a4f

I recently had an interesting conversation with some very smart colleagues on the topic of UI component reusability on modern web frontend applications. This led me to spend a few hours on a lazy Sunday afternoon clarifying my thoughts on the status quo - and the road ahead - of web frontend architectures. Specifically, I find it interesting how the short-term future seems to somewhat contradict the assumed long-term future.

The Short-term Future

React is currently the “new hotness” and seems like the immediate future of the web frontend, and along with it come a breadth of projects that either build on top of the framework itself, or on the ideas it has recently popularized. The most important of these ideas is the concept of a “Virtual DOM”, which reduces the need to directly operate on the massive, complex & sluggish shared data structure that is the traditional DOM. React et al have been covered thoroughly elsewhere, so let’s move on.

In the wake of React comes another important part of the immediate future of frontend development: the Flux architecture and its kin. At this point it’s important to note that neither React’s virtual DOM nor the Flux architecture are necessarily the pinnacles of their respective breeds (e.g. virtual-dom seems an excellent, simplified alternative to React, and there’s no shortage of Flux-like architectures lately), but for the purposes of this discussion, let’s treat them as umbrella terms for the ideas they represent.

The Flux architecture proposes to simplify complex Single-Page Applications through an unidirectional data flow. This is also explained better elsewhere; the point to take away from this is that things become simpler when you just re-render the entire application based on the current state of the world, a single, consistent source of truth, and do it every time that state changes. This reminds us of the good-old-days of server-side rendering, back in the 90’s: you have a bunch of data (e.g. request parameters, stuff from a database), which you turn into HTML by passing it through a function (e.g. your templating implementation). What makes this simple - and crucially different from traditional DOM wrangling in the browser - is you don’t have to care about any existing state in the DOM, you just overwrite everything with a new one. Traditional string-interpolation templating has been possible in the browser for ages, but constantly dumping and re-creating the DOM has been prohibitively expensive for larger apps. A Virtual DOM changes this.

So this is what I take the short-term future of web frontends to be, and indeed, the ball is already rolling. But is there something even bigger brewing?

The Long-term Future

Flux et al are still fully present-generation frontend architectures - that is, they’re working off of the same technical platform which has existed in browsers for a long time. In the distant horizon, however, a tectonic shift to this platform awaits: Web Components. I’ll again leave the introductions to more capable hands, but the gist of the spec is being able to create custom DOM elements. These custom elements are isolated from their neighbors (and parents and children), and they encapsulate some small part of the overall application. The canonical example is the <video> element: all you need to do to bring a self-contained video player “mini-application” - complete with a seek bar, playback control buttons, etc - into your main application is to create a <video> element on your page. The internal logic, DOM content, styling and (importantly!) state is neatly packaged away behind a standard DOM element. My go-to quote with this is from Justin Meyer: "The secret to building large apps is never build large apps. Break your applications into small pieces. Then, assemble those testable, bite-sized pieces into your big application". Web Components allows you to do just this: break your large app into a bunch of independent mini-apps, making the complexity of the whole more manageable.

Another important implication of being able to abstract these mini-apps behind a standard DOM element is interoperability. With the current state of affairs in frontend development, being able to reuse UI elements within and across projects is quite painful. Taking one JavaScript module and making it work together with one from another framework is hard enough, let alone being able to neatly package other UI assets (such as CSS, icons, images) to go along with it. Web Components (through the related HTML Imports spec) proposes a solution: a standard browser mechanism for pulling in a component - with all its dependencies and assets - and using it as simply as adding a <div> into the DOM. One core part of this promise is the aforementioned isolation: importing a custom element would have minimal effects on any existing elements within the same DOM. Many large frameworks such as Angular, Ember, Polymer and X-Tags are already preparing for this, promising (and even demonstrating) easier interoperability. The importance of this should not be understated: frameworks with very different ideas and implementations could be mixed at matched with relative ease. Given the incredible pace of innovation in the web frontend space, being able to swap out components for better ones without completely rewriting the surrounding application should be very desirable.

Sound good? I think so. I’m willing to go on record saying Web Components will be huge (thoroughly embarrassing my future self I’m sure, but oh well). Sadly, they’re not there yet: even though the most relevant frameworks in the space - Polymer (backed by Google) and X-Tags (backed by Mozilla) - have polyfills for parts of the spec, it represents such a fundamental shift to how applications are constructed and how browsers work that it won’t be smooth sailing for quite a while.

The philosophical mismatch

So these are the directions frontend development seems headed, now and later. However, these short- and long-term futures seem incompatible at face value: aren’t the intricacies of maintaining stateful DOM components exactly what Flux et al attempt to rid us of? If we have a “mini-app” with encapsulated internal state, we can’t just re-render the whole DOM whenever we feel like it, as we’ll potentially lose that important state. Or, from another perspective, if we no longer have control over (or even access to!) the full state of the world, how can we ever be sure that that state remains consistent across our UI? This mismatch seems quite fundamental, and I don’t see a simple way to unify these concepts.

Yet, that is not to say there’s no potential for overlap. Indeed, the promised interop mechanisms of Web Components are too important for the frontend profession to overlook. It should also be noted Flux itself proposes striking a balance between the simplicity of a single, unidirectional flow of data (with a singular state of the world) and breaking a large application into smaller, more manageable sub-applications (with their own internal states). The reasoning behind this is a familiar one: we human beings are pretty terrible at handling a lot of complexity at once, so it helps to break problems into manageable chunks. So, if these two futures aren’t mutually exclusive after all, how would we go about marrying them, and would we get anything out of it?

A brief aside on loosely connected sub-domains

Say you were building a web shop, ignoring the choice of frontend architecture for a bit. The domain of that application would probably include displaying product categories, the products that belong to those categories, a shopping cart implementation, that sort of thing. The information within that domain - the price of a product, for instance - has to remain consistent regardless of where the product appears (e.g. in a listing, a shopping cart or a wishlist). Giving this domain the “mini-app” treatment, and breaking it apart into smaller chunks might not reduce the overall complexity of the system at all. The opposite seems likely, in fact, as making sure the state of the world remains consistent across a distributed system is far from trivial.

Consider, however, having to add a chat-box next to your product details page. You know, the kind where you can talk to a human if you can’t figure out if the product is right for you. Implementing a real-time chat application in the browser is certainly easier than it used to be, but it isn’t trivial, either. Facebook had problems getting theirs right, and they employ pretty smart people. But even though the chat-box contains an interesting domain of engineering challenges, it’s only loosely connected to the domain of the main application. That is, while the sales representative at the other end might be interested in a bit of context - like the current product category - the chat-box can lead a relatively independent life.

But I digress; what does any of this have to do with marrying React, Flux & Web Components?

Packaging Flux into Web Components

One way to leverage both paradigms is to implement the internals of a Web Component using a Flux-like architecture. If you’re able to identify a loosely connected sub-domain in your frontend application - the chat-box for example - you can use all the simplifying power of Flux within that domain. Packaging it up in a Web Component gives you a custom element - say <chat-box> - which is both agnostic to its surroundings, and reusable within and across projects. Pulling it in is as simple as adding a <link rel="import" href="chat-box.html"> to your application, and it’s ready to be used wherever you could drop any other tag, say, <video>.

The <chat-box> element presents itself as a regular DOM element, and exposes any custom functionality through standard DOM API’s, the ones all kinds of libraries already know how to manipulate:

  • To parametrize it during instantiation, you can pass in attributes:
    <chat-box product-category="bikes">
  • To invoke its encapsulated functionality, you can call methods on the DOM element:
    document.querySelector("chat-box").connect()
  • To react to interesting changes in state, you can listen for events:
    chatBox.addEventListener("customer-rep-available", highlightChatBox)

The true (proposed) power of Web Components is realized when the packaging of such a component is so opaque that it doesn’t really matter which framework reigns inside (or outside!) the component. Let us assume Angular 2.0 comes out and you find it solves the real-time chat domain problems with a lot more elegance than with Flux. As long as the new implementation exposes the same standard DOM interfaces, swapping in an Angular implementation would be as simple as changing the HTML Import for the <chat-box> element.

Managing Web Components within Flux

The pairing also works the other way around. Since a Virtual DOM implementation essentially allows you to create lightweight DOM elements (which are realized into their heavyweight counterparts only as necessary), there’s no reason why custom elements can’t receive the same treatment. That is, if our React component’s render method looks like this:

render: function() {
    return <div>
        Hello {this.props.name}!
        <chat-box />
    </div>;
}

...then there’s no reason to assume the <chat-box> element has to be implemented as a React component. Indeed, exactly as you’re able to create a virtualized <div> which will later be turned into an actual DOM <div>, we can let React manage the life and death of our custom <chat-box> element. Again, what makes this possible is the fact that custom elements are integrated through standard DOM API’s, which DOM libraries (like React or jQuery) already know well.

The problematic part of this approach is that in order to be fast, React will often reuse existing DOM elements to realize DOM updates as cheaply as possible. If our custom element contains internal state, it will likely end up either lost or corrupted as a result. While the problem can be somewhat worked around with keyed children, we still need to be careful to not lose important internal state along with the comings and goings of custom elements.

Managing stateless Web Components

The two previous approaches to combining a Virtual DOM with Web Components have been very much concerned with state management, and rightly so: managing the consistency of state in a complex frontend application can be a daunting task. If we forget Flux, state management and our beloved “mini-apps” for a bit, however, we’re still left with an interesting case to consider: Web Components without any internal state.

Let’s say you’ve created a sweet tab implementation as a Web Component, and decided its markup looks like this:

<paper-tabs selected="0">
    <paper-tab>First tab</paper-tab>
    <paper-tab>Second tab</paper-tab>
    <paper-tab>Third tab</paper-tab>
</paper-tabs>

The "selected" attribute controls which tab is currently visible. We can easily let React manage this tab implementation, without fears of clobbering delicate internal state, like so:

render: function() {
    return <paper-tabs selected={this.props.selectedTab}>
        {tabContent}
    </paper-tabs>;
}

The important difference to the preceding <chat-box> example is that any and all state in the <paper-tabs> component is explicitly controlled through immutable props. This is a good thing. It allows us to simply not care how React manages the DOM, as long as the end result has the content and attributes we specify. It also allows us to fall back to the simple, unidirectional data flow of Flux, while still enjoying many delicious properties of Web Components, such as their isolation semantics, and delivery through simple HTML Imports.

Closing thoughts

I hope this helped clarify how the two futures of frontend architecture - Flux and Web Components - are fundamentally different, and yet still composable in interesting ways. The simplifying power of Flux can’t be dismissed, but even less so the powerful interoperability characteristics of Web Components. While an acceptable level of Web Components support is still years away, if we align our choices today with what’s coming tomorrow, we’ll be on more solid ground as the craft continues to evolve, often at breakneck speeds. As software craftsmen, we want to use these latest advances in technology to deliver kick-ass user experiences for the browser. Currently, the “new hotness” is React, with a Flux-like architecture backing it up. Still, it’s certainly not the end-all of frontend architectures, and big players are betting big on Web Components. Understanding the interplay between these short- and long-term futures is important to make sure our creations stand the test of time.