I’ve seen many, many people using a modern framework like React, Angular or Vue.js blindly. These frameworks provide lots of interesting things, but usually people miss the point about the deepest reason of their existence, which is not:
- They are based on components;
- They have a strong community;
- They have plenty of third party libraries to deal with things;
- They have useful third party components;
- They have browser extensions that help debugging things;
- They are good for doing single page applications.
The essential, fundamental, deepest reason is this one:
Yes, that’s it, and let’s see why
Imagine you are implementing a web application where the user can invite many people by introducing their email addresses. The UX/UI designer makes this decision: there is an empty state where we show a label with some help text, in any other case we show the email addresses with a button/link to delete them to the right.
The state of this form can be basically an array of objects containing the email addresses plus a unique identifier. Initially it will be empty. When adding an email address by typing it and hitting enter you add the typed email address to the array and update the UI. When the user clicks on “delete” you delete the email address and update the UI. Have you seen that? Every time you change the state, you need to update the UI.
So what? Well, let’s see how we can implement it with out the help of any framework:
A vanilla implementation of a somewhat complex UI
innerHTML and it could have been more readable but it’s less efficient and can cause Cross Site Scripting bugs. We could have used a templating engine as well, but if you regenerate a big subtree of the DOM you have two problems: is not very efficient and you have to usually reconnect the event handlers.
But that’s not the biggest problem. The biggest problem is always updating the UI on every state change. Every time the state is updated there is a lot of code required to update the UI. When adding an email address in the example it takes 2 lines of code to update the state, but 13 lines to update the UI. And we made the UI as simple as possible!!
It is not only hard to write and hard to reason about, but more importantly: it is also extremely fragile. Imagine we need to implement the ability to sync the list with a server. We will need to compare our data with the data received from the server. This involves comparing all the identifiers and the content (we could have in memory a copy of a record with the the same identifier but with different data).
We will need to implement a lot of ad-hoc presentation code to mutate the DOM efficiently. And if we make any minimal mistake, the UI will be out of sync from the data: missing information, showing wrong information, or completely messed up with elements not responding to the user or even worse, triggering wrong actions (e.g. clicking on a delete button deletes the wrong item).
So, maintaining the UI in sync with the data involves writing a lot of tedious, fragile, and fragile code.
Declarative UIs to the rescue
So it is not the community, it is not the tools, nor the ecosystem, nor the third-party libraries,…
The biggest, by far, improvement these frameworks provide is having the ability to implement UIs that are guaranteed to be in sync with the internal state of the application
Well, almost. It is true if you don’t mess with some rules that each particular framework might have (e.g. immutability of the state).
We define the UI in a single shot, not having to write particular UI code in every action, and we always get the same output due to a particular state: the framework automatically updates it after the state changes.
How does it work?
There are two basic strategies:
- Re-render the whole component: React. When the state of a component changes it renders a DOM in memory and compares it with the existing DOM. Actually since that would be very expensive it renders a Virtual DOM and compares it with the previous Virtual DOM snapshot. Then it calculates the changes and performs the same changes to the real DOM. This process is called reconciliation.
- Watch for changes using observers: Angular and Vue.js. Your state variables are observed and when they change only the DOM elements where those values are/were involved in the rendering are updated.
What about web components?
Many times people compare React, Angular and Vue.js with web components. This is a clear indicator that many people do not understand the biggest benefit these frameworks provide: keeping the UI in sync with the state. And web components doesn’t provide anything for that. They just provide a <template> tag but it doesn’t provide any reconciliation mechanism. If you want to use web components and have the UI in sync with the internal state of your app you have to do it by hand, or you can use something like Stencil.js (which internally uses a Virtual DOM, like React).
Do it yourself
I love to learn the fundamentals of things and it turns out that there are Virtual DOM implementations out there. So, why don’t we try to rewrite our vanilla UI implementation just using a Virtual DOM implementation without the help of any existing framework?
Here’s the core of the framework, the base class for any component.
And here’s the reimplementation of the AddressList component (with the help of a babel transform to support JSX):
Now the UI is declarative and we haven’t used any framework. We can implement new logic that changes the state in whatever way, and we don’t have to write additional code to keep the UI in sync. Problem solved!
Now, except for the event handling, this looks like a lot like a React app, right? We have
setState() ,… Once you solve the problem of keeping the UI in sync with the internal state of the application, everything else stacks up naturally.
You can find the full source code in this Github repository.
- Web components do not provide a solution to this problem.
- It’s not that hard to make your own framework using an existing Virtual DOM library. But I’m not suggesting you to do that!