From d98c979f39496bbcac45d463f6a1469c270a8ff5 Mon Sep 17 00:00:00 2001 From: Ayo Date: Thu, 21 Dec 2023 17:57:59 +0100 Subject: [PATCH] new post: reactivity base class --- Gemfile.lock | 12 +-- _drafts/introducing-astro-reactive.md | 6 -- _drafts/solidjs-fine-grained-reactivity.md | 5 -- ...ctive-custom-elements-with-html-dataset.md | 80 +++++++++++++++++++ 4 files changed, 86 insertions(+), 17 deletions(-) delete mode 100644 _drafts/introducing-astro-reactive.md delete mode 100644 _drafts/solidjs-fine-grained-reactivity.md create mode 100644 _posts/2023-12-15-reactive-custom-elements-with-html-dataset.md diff --git a/Gemfile.lock b/Gemfile.lock index 207a6a7..f8a9794 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.4) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) colorator (1.1.0) concurrent-ruby (1.2.2) @@ -10,11 +10,11 @@ GEM http_parser.rb (~> 0) eventmachine (1.2.7) eventmachine (1.2.7-x64-mingw32) - ffi (1.15.5) - ffi (1.15.5-x64-mingw32) + ffi (1.16.3) + ffi (1.16.3-x64-mingw32) forwardable-extended (2.6.0) http_parser.rb (0.8.0) - i18n (1.13.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) jekyll (4.2.2) addressable (~> 2.4) @@ -54,11 +54,11 @@ GEM jekyll-seo-tag (~> 2.1) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (5.0.1) + public_suffix (5.0.4) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.2.5) + rexml (3.2.6) rouge (3.30.0) safe_yaml (1.0.5) sassc (2.4.0) diff --git a/_drafts/introducing-astro-reactive.md b/_drafts/introducing-astro-reactive.md deleted file mode 100644 index aaa0b5d..0000000 --- a/_drafts/introducing-astro-reactive.md +++ /dev/null @@ -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. - diff --git a/_drafts/solidjs-fine-grained-reactivity.md b/_drafts/solidjs-fine-grained-reactivity.md deleted file mode 100644 index 7c02171..0000000 --- a/_drafts/solidjs-fine-grained-reactivity.md +++ /dev/null @@ -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. - - diff --git a/_posts/2023-12-15-reactive-custom-elements-with-html-dataset.md b/_posts/2023-12-15-reactive-custom-elements-with-html-dataset.md new file mode 100644 index 0000000..41e895f --- /dev/null +++ b/_posts/2023-12-15-reactive-custom-elements-with-html-dataset.md @@ -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 →](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). ✨ \ No newline at end of file