I was a casualty of the Version 4 Browser Wars.
Those of you who are old enough to remember know exactly what I’m talking about. For those who weren’t there, maybe I’ll write a post about it someday. But anyway, I became a desktop developer for many years as a direct result of that horrible early web experience. It is only recently (the last few years) that I’ve cautiously stepped out into frontend development again.
I say this to emphasize that I’m not an expert frontend developer (yet). I’ve dabbled in Knockout, Angular, Durandal, and Aurelia. I’m much more familiar with common libraries like lodash and jQuery. But when it came to choose a framework for DotNetApis, I chose something I had never used before: React.
Well, in short, because Cory House convinced me. I was able to attend his excellent talk at ThatConference 2015 Angular, Backbone, and Knockout are great, so why choose React?, and followed that up with his Pluralsight course on React and Flux. Cory House is convincing. :)
It all boils down to this simple fact: React represents component boundaries as files. In other words, a single component belongs in a single file.
In a modern MVVM-style data-binding system, you usually end up with pairs of files. You have the view and the view model:
I’ve written a few components like this, enough to know something’s wrong. In particular, when you have this kind of structure in your app and you need to modify a component, which file do you end up modifying? Usually both of them. New functionality generally requires both the view and view model to change. This is an indication that the view and view model are really dealing with the same underlying concern.
According to Wikipedia, the Single Responsibility Principle is “every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.” Take special note of the second half of that definition: “that responsibility should be entirely encapsulated”. That’s why it’s harder to have multiple files making up a single component. There’s a lack of cohesion.
None of these hurdles are insurmountable, of course. Large systems have been written with these techniques, and they work well. But we can do better.
React simplifies component work. Then it gets out of your way.
My First Attempt (Flux, not Redux)
React only addresses the “view” part of the frontend. It doesn’t have any opinions about how you store your data. There’s a pretty common pattern of data management called Flux.
When I started using React, I decided to go with “vanilla Flux”; that is, to have a singleton dispatcher implementation. Flux works like this:
You have “action creators”, which are just functions that return actions. Those actions are data (objects) that represent some action, such as a user clicking a button or typing into a textbox. The actions are sent to the dispatcher, which is a singleton. The dispatcher then sends out the action to each store that has registered with it (this is just a simple pub/sub pattern).
The stores are where the application state lives. Each store updates itself by responding to actions coming from the dispatcher. Each time it updates, it notifies its subscribers, which are the views (the actual JSX components). Those views in turn will respond to user interaction by calling the action creators and dispatching those actions.
This diagram is a bit simplified; the “Views” here are really made up of two different kind of views: “smart” or “page” or “container” or “route” views, which sit at the top level and subscribe to store updates, and “dumb” views, which only receive data from their parent views.
This is all well and good, but as DotNetApis grew more complex, I started running into problems.
In particular, I had several different “stores”, each one containing part of the application state, divided up logically. Also, I had several different “smart” views, some of which were children of other “smart” views, and each of which had to manage subscriptions to one or more stores. What I ended up with looked more like this diagram:
Redux is a further simplification of React. Redux declares that you should only have one store, and all of the application state lives in that single store.
Now, Redux does introduce some new concepts. In short, it implements your store for you, so you need to fill in a few missing pieces.
In particular, you need to define how actions will change your application state. In Redux, these are called reducers.
A real-world Redux application ends up looking more like this (note that the shaded parts are mostly or entirely implemented by libraries, not by the application author):
Once I started using Redux, I’ve never looked back. Everything is simpler!
The source hasn’t changed since last time, but if you missed it: