Granular and Coarse Reactivity

One way to categorize reactive JavaScript programming paradigms is by granularity: fine/granular and coarse reactivity. There are pros and cons to each approach, and it is worth considering these approaches to decide on which may work better for your application, or even how you might integrate the two approaches.

Granular reactivity is an approach where each different pieces of time-varying state is stored in different streams, or variables. We can create many different reactivity variable to contain the various parts of application state that may change. These various variables are then used to explicitly control different parts of the UI that are dependent on this state. This approach is facilitated by libraries like BaconJS, RxJS, and Alkali.

Course reactivity takes a more bundled approach. All of the state that affects a given component is bundled into a single state object or container. When state changes, rather than discerning the separate aspects of each state change, an entire component is re-rendered. By itself, this would be tremendously inefficient, as you would have a large scale DOM rewrite on every state change. Consequently, this approach is almost always combined with a diffing mechanism, which involves virtual DOM (an in-memory copy of a DOM), that can compare the results of the rendered output to compute exactly what DOM elements need to be changed, and minimize DOM modifications. This approach is most famously implemented in the React library.

Again, there are certainly advantages and disadvantages of each approach. Course reactivity has gained tremendous popularity because it facilitates the creation of reactive UIs, while still allowing developers to use familiar imperative style coding. This style, sometimes combined with syntactic additions (JSX), provides a terse syntax for writing components, with familiar style and elements.

There are distinct disadvantages though, as well. By avoiding any explicit connection between indvidual components of state, and their resulting output, reactive changes result in large scale rewrites of component output. Again, to cope with this, coarse reactivity employs diffing algorithms to precisely determine what has changed and what hasn’t. However, this comes with a price. In order for the diffing to work properly it must maintain a virtual copy of the DOM so it can compare changes before and after reactive changes to determine what DOM elements to actually update. This virtual copy consumes a substantial amount of memory, often require several times as much memory as the corresponding DOM alone.

Memory consumption is often trivialized (“memory is cheap”), and easily ignored since it doesn’t usually affect isolated performance tests. However, high memory usage is a negative externality, there are many layers of caching that it slows down, it incrementally drags down not only itself, but other code in the system. This is similar to real-world externalities like pollution; in isolation it is not worth dealing with, but collectively, when everything is consuming high amounts of memory, the effects become more visible.

The imperative nature of coarse reactivity also complicates efforts to create two-way bindings. Because the relationship between data sources and their output is obscured by the imperative process, there is no natural connection that relates input changes back to their sources. Of course, there are ways to set up bindings back to data, but this doesn’t come for free.

On the other hand, using a more pure, granular approach to reactivity, we can overcome these issues. This has challenges as well. The most obvious drawback of granular reactivity it requires a more functional approach, rather than the familiar imperative approach. However, this can also be considered a benefit. Granular reactivity pushes developers towards using more functional paradigms. Functional programming has substantial benefits. While these benefits can be explored in far more depth, briefly it leads to a more direct way of describing “what” is happening in an application, rather than working in terms of “how” a sequence of state changes will accomplish a task. This is particularly important as we read and maintain code. While we often think about coding in terms of writing new code, most of us spend much more time reading and updating existing. And being able comprehend code without have to replay state sequences is a huge boost for comprehension.

And granular reactivity certainly provides a more memory efficient approach. Only those elements that are bound to data or variables have extra memory, and only the memory necessary to track their relationship. This means that static portions of an application, without any data binding or backing variable, do not require any extra memory elements (in a virtual DOM, or otherwise). In my work with a highly memory intensive application, this has been a tremendous benefit.

Granular reactivity also creates direct relationships between state and UI component values. This greatly simplifies two-way data bindings, because UI inputs can be directly mapped back to their source. This two-way connection can automatically be provided by a good granular reactive system.

Finally, granular reactivity can more easily be incrementally (and minimally) added to existing components and UI applications. Rather than having to convert whole components to using virtual DOM output, individual reactive elements can be incrementally added to components with very minimal changes.

While these are contrasting approaches, it is certainly feasible to combine them in a a single application. One can use coarse state components (with a virtual DOM) for portions of the application that are may not incur as much memory overhead (perhaps due to low frequency of usage), while areas with more heavy usage, or that will benefit from more automated two-bindings, can use granular reactivity approaches. Either way, it is important recognize the pros and cons of these approaches. While coarse grain may be quick and easy, deeper concerns may suggest the more efficient and functional approach of granular reactivity.