Reactive programming, name derived from the pioneer React framework, is a concept that has gained acceptance in user interface (UI) development – inside and outside the Web. The reactive Web frameworks in current evidence are React and Vue.js. For mobile and desktop, there are React Native, Weex and Flutter.
As most great ideas, the core concept is very simple. Suppose a Web page generated from a template hydrated with properties. If some property changes, the whole page is regenerated. It is that simple!
It looks inneficient, but React implemented an ingenious optimization: just apply the differences to DOM. Manipulating the DOM is by far the most expensive operation in a Web app. Calculating the differences between old and new version of a page is relatively cheap.
Yeah, it is still a bit more efficient to change only the exact DOM points, instead of creating a virtual representation of the whole page to calculate the differences. But the developer productivity gains in exchange are so huge to be ignored.
In traditional UI development, it is necessary to somehow monitor changes in properties, and invoke UI updates in response. The reactive frameworks use some tricks to automate this monitoring. The page reacts automatically to changes — effect that baptizes the framework and the concept.
Reactivity itself allows for another optimization: when a property is changed, only the dependent components react. Assuming the Web page follows a hierarchy of components, typically a change in a property affect a small portion of the page, and the difference algorithm just needs to work on that portion.
Reactivity lets the concept of functional programming to jump to life from the comp sci bookshelf. A pure function is the one whose result depends on arguments only. For example, we can trust that abs(-3) is always equal to 3. On the other hand, the result of fopen("foo.txt") depends on external factors.
In a reactive framework, the UI state is a pure function of the properties, and the UI state is always consistent in relationship to the properties. The developer needs only to worry "what is to be shown", not "how to show".
A big challenge in traditional UI programming is to keep sync between the app state and what is shown on screen. It is very easy to drop the ball. Using reactive techniques resolves this problem once and for all.
In order to keep the UI as a pure function of properties, the UI must never change itself directly. Suppose the name of a button should change when clicked. Typically, we implement this at the click handler. In a reactive framework, the event handled should only change some property, which in turn the button name depends on. This is the concept of one-way binding.
Since the changes flow in one direction only, another typical problem goes away: the dreaded infinite loops when an event handler changes the UI, which triggers another handler, which changes the UI again and so on. It is still possible to force an infinite loop to happen in a reactive framework, but the odds of this happening accidentally are smaller.
React sticks to this concept very far. Even typing inside a Web form input is blocked by default, because it would change the DOM; React detects this change in DOM and undoes it. If you want a functional form input in React, you need to implement a keyboard event handler which changes some property, and the value attribute of the input should depend on that property.
This is a polemic characteristic. On one hand, React's approach guarantees absolute consistency. On the other hand, it throws away the browser's implementation and some think it to be excessive — after all, forms are meant to be filled, right? Other frameworks like Vue.js implement two-way binding and allow the "natural" form filling, and values are automatically reflected on propreties (reverse reacting).
Up to this point, we have said that UI is a pure function of properties, we can now expand this nomenclature a bit.
In React/Vue.js jargon, properties or "props" are immutable parameters of an UI component. The parent component supplies properties to children components. If the parent wants children with different properties, it should generate new children, because the old "generation" is immutable.
A component can also contain mutable data, which is the internal state. For example, the internal state of a clock component is, at least, the current time. Accordingly to the "reactive doctrine", this kind of information should only be seen by the component itself, not to shared with others, unless in the form of properties passed to children components (e.g. a clock component may have hours, minutes and seconds as children).
The application state looks like the Model of the MVC architecture. It is a data repository that does not belong to any particular component. It is the top of data hierarchy since probably the whole UI, with all properties of all components, are ultimately derived from the app state.
It is possible to store the app state in the most senior component. But, in non-trivial apps, it is better to use some auxiliary framework that is tailored for the task (e.g. Redux with React or Vuex with Vue).
It is not always obvious where a certain piece of information should be stored. For example, a clock could store the current time in three ways, and would work in any case:
The state managers Redux and Vuex, already mentioned, are the most popular. Whoever is the manager framework, it blocks the unorderly manipulation of the state. The state can only be change through mutation methods.
In a well-architected app, the mutation are analogous to database transactions, business rules, or transitions of a finite automaton.
Suppose a list of products with a color filter. When the user selects a color, a mutation is invoked. In a "correct" app, this mutation should be named "filterByColor" and would receive the color as parameter. The mutation is related to a high-level user choice, and the filtering machinery is implemented within the state manager, hidden from the UI code. On the other hand, a "bad" app would implement the filter right at the event handler, and would invoke the mutation "showThisProduct" many times, once per item that matches the color.
Vuex is remarkably simple to use. There are "mutations" and "actions", the latter can have collateral effects and use asynchronous code. For example, a REST call must be implemented as an action, which in turn invokes mutations accordingly to the result of the call.
Redux has a more elaborate, if analogous, architecture with "reducers" and "actions". The reduces change the state, while actions can have collateral effects. The main difference to Vuex is the uncoupling. An action cannot invoce reducers directly; it just returns a "record" describing what it did. Then, the record is forwarded to the appropriate reducer.
Likewise, the coupling between app state and components can be higher in Vue/Vuex. Vue components can access the app state directly via this.$store.state. This is discouraged in Redux; the preferred way is to use the idiom connect(mapStateToProps).
Every software developer is indoctrinated to work apps as independent layers. In a Web app, the layers are HTML (semantic), CSS (appearance) and Javascript (code). Frameworks like Angular follow this approach: every component has at least three source code files, one per layer.
React has subverted this logic. A React component is 100% Javascript and needs a single file. It generates HTML using Javascript and encourages dynamic CSS generation as well (the latter depends on an auxiliary framework). There are no templates or template languages. There are good reasons for that:
React offers JSX, a Javascript dialect that supports embedded HTML syntax, with vague resemblance to PHP. It is another polemic feature. Some HTML attributes are renamed in JSX to avoid conflicts with Javascript keywords, or because they are different across browsers. One could say the "HTML" embedded in JSX is actually a DSL in disguise. Another valid concern is the bigger difficulty to delegate Web design to non-developers.
Personally, I didn't like JSX. Vue.Js seems to have reached a sweet spot in these matters. A Vue component can be implemented with a single file; inside the file there are separate sections for template, code and CSS (whose scope can be automatically restricted to that component). The Vue.js template DSL is very simple and ergonomic, it is indeed the easiest part of the framework.
Even though this is not exclusive characteristic of reactive Web frameworks, another change of paradigm they bring is the pervasive AJAX, that is, all content is generated by Javascript. By default, every React or Vue.js app is a single HTML page (called SPA, "single-page app"). The base, static HTML is just an empty shell. If Javascript is disabled, the page does nothing and shows nothing. It is the opposite of traditional Web.
Yet, it is often desirable to keep the ilusion of navigating pages within an SPA app, with functional bookmarks and history. The navigation simulation is carried out by a router. In theory, both Vue and React can use any router framework. In practice, Vue Router and React Router are used, respectively.
100% AJAX conent is the trend, but it is not always desirable. One problem is SEO: search machines have, at best, limited ability to run Javascript. They tend to see just the static, initial HTML contents. Another problem is compatibility with certain browsers. And some users do prefer to disable Javascript due to security concerns.
There are two alternatives to pure AJAX: server-side generation (SSR) and prerendering.
Prerendering is the generation of static complete HTML pages. It is adequate when the content is static as well. It may be the case that prerendering a handful of pages is enough to achieve the SEO needs. Once rendered, any Web server can serve such pages.
SSR keeps the dynamic character of pages, but runs the Javscript code at server side, which demands Node.js servers (or AWS Lambda).
Both Vue.js and React ecosystems offer SSR and prerendering tools. But SSR is not a free lunch. If you are writing an app that intends to be SSR, it must be engineered with SSR in mind. A non-SSR app takes many modifications to become SSR, so it is best to plan in advance when possible.