If you can use ES6 template strings, you can write reactive, componentized views using less than 500 bytes of helper code.
Let me be clear. This approach isn’t intended as a serious replacement for React+Redux/RxJS. It doesn’t re-render views very quickly and the way it handles events is verbose. It’s simply an experimental way to write light, simple applications that need to load fast, without the hassle of transpiling JSX or rendering on the server. I’m sharing it as a curiosity more than anything else.
- We store our state in observables – objects that let us subscribe to their changes with callbacks.
- We create HTML for each component in computed observables – observables that read other observables, and automatically update with them.
- We generate our view with a ‘master’ computed observable that read all the components’ computeds.
- When that master observable changes, we update the DOM.
- We make the observables and computeds work using a reactive microlibrary. Trkl, minified and gzipped, does the job in a little over 400 bytes.
That’s a lot to take in. Let’s first take a moment to understand these observables and computed observables, so we can then see how they fit together.
Observables are values whose changes we can subscribe to with callbacks. When the observable changes, the callbacks are run.
1 2 3 4
If we store our application’s model in observables, we can then read it with computed observables.
Understanding ‘computed observables’
A computed observable is an observable that runs a function, takes note of the observables that function reads, and re-runs the function whenever they change – updating itself in the process. A computed is an observable itself, and it can be subscribed to by other computeds.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
How computed observables work is a little out of the scope of this post. Essentially, whenever we run an computed, we set a flag that tells any observables being read that they must subscribe the computed to their changes. When those observables next change, the computed gets re-run automatically.
Don’t worry if you’re still fuzzy on exactly how they work. The most important idea to grasp is that if our model is written with observables, our view can be written as a computed that is always updated when the model changes.
Here’s a simple example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
This works, but remains too unwieldy to use on anything beyond the simplest views. We need to refine it.
If we can use ES6 template strings – if we don’t care about anything beyond bleeding-edge browsers, or if we’re willing to transpile our code – we can embed HTML markup directly in our view function.
1 2 3 4 5 6
Now, that’s fine for two lines of HTML, but what if our view gets more complex? We hardly want to write all our markup in a single function. Won’t it grow out of control after five or six lines? And what if we want to reuse the same component in multiple places? We need to separate different parts of the view, rendering each component in its own computed, and subscribing to each computed in our ‘root’ view:
1 2 3 4 5 6 7 8 9 10 11 12 13
para will update.
Para is an observable like any other, so the view can subscribe to it.
Ignore redundant re-renders
If we change
lastname at the same time, our view will render twice – one time with the first changed property, and another time with both. This is expensive, because every time we render the HTML, we force the browser to reparse the entire view’s HTML.
Some observables libraries are smart enough to handle this problem for us. Trkl – which I wrote – isn’t. It simply does the minimum necessary in the minimum bytes – that’s why it’s small. But we can still throttle redraws in a very simple way: whenever the view needs to change, we put our new markup in a variable, and then queue a redraw for the next frame if we haven’t already. On subsequent updates, we just update the variable, so that the queued-up redraw will output that, instead. This means the DOM only changes once.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
One shortcoming of only dealing with HTML strings, rather than actual DOM objects, is that we can only add events by adding inline event handlers to our markup. This is verbose and only allows events to call functions that are globally accessible on
window. I have to admit that this is a real shortcoming of the approach. Still, if you’re building something basic enough, you might decide you can bear it.
Here’s what a form input might look like:
1 2 3 4 5 6 7 8 9 10 11 12
Be careful how many events you attach. Remember that because the entire view is redrawn each time, every re-render means tearing down the existing event handlers and recreating new ones.
Putting it all together
You can see an example of this approach in a recent experiment of mine – a hands-free recipe app that uses computed observables and template strings to render the view. When the model is mutated, the view responds instantly, and without requiring any templating library or heavy dependencies.
Tempted to give it a try yourself? Have a go in your next experiment, and tell me how you get on in the comments below!