new post: reactivity base class

This commit is contained in:
Ayo 2023-12-21 17:57:59 +01:00
parent 15fe7a5966
commit d98c979f39
4 changed files with 86 additions and 17 deletions

View file

@ -1,7 +1,7 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
addressable (2.8.4) addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
colorator (1.1.0) colorator (1.1.0)
concurrent-ruby (1.2.2) concurrent-ruby (1.2.2)
@ -10,11 +10,11 @@ GEM
http_parser.rb (~> 0) http_parser.rb (~> 0)
eventmachine (1.2.7) eventmachine (1.2.7)
eventmachine (1.2.7-x64-mingw32) eventmachine (1.2.7-x64-mingw32)
ffi (1.15.5) ffi (1.16.3)
ffi (1.15.5-x64-mingw32) ffi (1.16.3-x64-mingw32)
forwardable-extended (2.6.0) forwardable-extended (2.6.0)
http_parser.rb (0.8.0) http_parser.rb (0.8.0)
i18n (1.13.0) i18n (1.14.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jekyll (4.2.2) jekyll (4.2.2)
addressable (~> 2.4) addressable (~> 2.4)
@ -54,11 +54,11 @@ GEM
jekyll-seo-tag (~> 2.1) jekyll-seo-tag (~> 2.1)
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
public_suffix (5.0.1) public_suffix (5.0.4)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
rexml (3.2.5) rexml (3.2.6)
rouge (3.30.0) rouge (3.30.0)
safe_yaml (1.0.5) safe_yaml (1.0.5)
sassc (2.4.0) sassc (2.4.0)

View file

@ -1,6 +0,0 @@
# What is Astro and Why Does it Exist?
Earlier this year I had the urge to rewrite my personal website. It was written in Jekyll, a Static Site Generator (SSG) in Ruby, which I only picked up because I wanted to use Github Pages years ago, and I think it was the best SSG framework to deploy on it at the time. My experience with GH Pages was not good--maybe it was too early?--so I almost immediately moved to using Netlify, which was also relatively new but offered way better experience and control.
For a while, Jekyll was a good framework for my needs, especially partnered with Netlify. But several major versions and many different computers later, setting up the environment became a headache. Add to this the compatibility issues with the new M1 Mac--I just don't think the time it took to look for what's wrong was worth it. It would be faster to write everything in HTML.

View file

@ -1,5 +0,0 @@
# Leveraging Solid.js for Fine-Grained Reactivity
Forget everything you know. This is Solid.js' invitation for us who have been so used to the bloated way of doing things in the JavaScript land, as it shockingly gives us a peek of a JS paradise where reactivity is built in and not a mere afterthought.

View file

@ -0,0 +1,80 @@
---
title: Reactivity? You Don't Need a Base Class for That
permalink: /:title/
description: "A demonstration of using standard HTMLElement APIs for reactivity"
category: technology
---
If you have ever looked up "how to write web components", you may have seen several approaches suggesting to use a base class.
I agree! To me, this was immediately apparent because I din't want to write a lot of the boilerplate every time I need a custom element.
But I sometimes see "reactivity" getting touted as a major feature of base classes and thought I'd write this quick one to show, well, you don't need a base class for *just that*.
## What is reactivity
The essence of reactivity is this: when a state changed, the view is immediately updated. They are synchronized.
JS frameworks have different approaches for this, with some better than others depending on certain situations. (It's a fun topic to nerd about.)
For components, this is commonly expressed as: if my property changed, the UI should be updated.
Let's not dig into details of implementing a reactivity system, but go straight into some vanilla implementation
## Vanilla Implementation of Reactivity
Here we want to use standard APIs available for custom elements: `HTMLElement.dataset` and the `attributeChangedCallback()` hook.
Let's consider the following code example (also [in CodePen &rarr;](https://codepen.io/ayoayco-the-styleful/pen/abMbvbo?editors=1010)):
```js
class Counter extends HTMLElement {
static observedAttributes = ["data-count"];
connectedCallback() {
this.dataset.count = this.getAttribute('data-count') ?? 0;
this.onclick = () => ++this.dataset.count;
}
attributeChangedCallback(prop, oldValue, newValue) {
if (prop === "data-count" && oldValue !== newValue) {
this.textContent = newValue;
}
}
}
customElements.define("my-counter", Counter);
```
In this example we use the standard `dataset` that collects all attributes prefixed with `data-`. `HTMLElement.dataset` exposes them for reading and writing and, because they have attribute counterparts, they will trigger the `attributeChangedCallback()` when modified and if they are observed attributes we explicitly indicated via `static observedAttributes`.
Hence, every time the user clicks the element, the program modifies the observed `data-count` attribute and the update to the UI via `textContent` is triggered.
The standard for `data-` prefixed attributes exists to make sure they don't override standard HTML attributes. And collecting all values behind the `dataset` property means no standard HTML properties can also be overriden.
Nice! But what's the catch?
## The Catch in using HTMLElement.dataset
Every time we modify a property value using `dataset`, the method `toString()` is actually called under the hood for the value assigned. This is because HTML attributes can only hold strings.
Our example works here because JavaScript (being loosely typed) allows arithmetic operations on stringified numbers. :)
You will experience hiccups, for example, when you need a boolean property, because the stringified "false" is also "truthy".
## The Reactivity Problem
This is the problem reactivity systems try to solve: how do you synchronize state & UI and still restore correct types in your properties after the full reactivity loop?
In my minimal base class [WebComponent.io](https://webcomponent.io), my approach is simple... we use the pattern encouraged by `HTMLElement.dataset`, but use a standard [ES6 Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) `props`.
The `props` property of `WebComponent` works like `HTMLElement.dataset`, but a camelCase counterpart using it will give read/write access to any attribute, with or without the data- prefix.
And because it is a Proxy, we are able to intercept this read/write accesses and allow for behavior modification like restoring types.
Therefore, we get some advantage over the standard `HTMLElement.dataset`: that `WebComponent.props` can now hold primitive types 'number', 'boolean', 'object' and 'string'.
## What Now
That's it really. I still use vanilla HTMLElement custom elements, and in fact they are present on my project websites.
But for things that need reactivity with real JS values as properties... I have a tiny, minimalistic base class for that. :)
Check it out on [GitHub](https://ayco.io/gh/web-component-base). ✨