Yesterday, we learned about DOM diffing and data reactivity, and used my state-based UI library Reef as a teaching tool.
You may be onboard with this approach, but convincing your client or the rest of your team to ditch their beloved React or Vue for some tiny open source project is typically a hard sell.
Today, I'm going to show you how get a similar authoring experience using Preact, a 3kb alternative to React with the same API and some nice features React doesn't have.
Let's dig in!
Why Preact is better than React
Preact is better than React for a handful of reasons.
- Preact is 3-4x faster the start working, and renders UI updates 3x faster, too.
- Preact lets you write your templates with template literals instead of JSX and a compiler.
- Preact provides a way to manage state as a standalone object (React forces you to keep state in components).
Did I mention it's smaller, faster, and better? It is!
Loading Preact
For our purposes, we need three Preact methods: html()
, render()
, and signal()
.
The render()
method does DOM diffing. The html()
method converts our template literals into an object of elements that render()
uses under-the-hood to render things (it's like JSX for the browser). And the signal()
method creates a reactive data object, just like store()
does in Reef.
While the Preact docs provide a load from CDN option as their default, the signals()
method is not part of that documentation.
After some digging, I found a CDN that includes all of the methods we need. It uses ES modules, so we're going to have to add [type="module"]
to our script
element and import
our methods.
<script type="module"> import {html, render, signal} from 'https://cdn.jsdelivr.net/npm/preact-htm-signals-standalone/dist/standalone.js'; </script>
Creating reactive data
To make our data reactive, we need to pass it into the signal()
method.
// Reactive todo data let todos = signal(JSON.parse(localStorage.getItem('todos-preact')) || []);
You can then access the data object using the value
property, like this.
// Get the todo array let arr = todos.value;
Unlike Reef (and Vue) which react whenever any property in the array or object is changed, Preact signals only react when the main array or object (or primitive value) itself is changed.
That means we have to do kind of clunky stuff like reassign the entire todos.value
property whenever we change one of the items in our array.
The easiest way to do that is with array destructuring.
// addTodo() todos.value = [...todos.value, form.todo.value]; // removeTodo() let todosTemp = [...todos.value]; todosTemp.splice(index, 1); todos.value = todosTemp;
Tomorrow, I'll show you a trick for making that a bit easier to work with.
Using the html()
method for our templates
In the getHTML()
template function, we need to add the html()
method that Preact requires to use template literals instead of JSX.
The html()
method uses tagged templates, which means we omit the parentheses (()
) when running it.
/** * Create the HTML based on the app state */ function getHTML () { // If there are no todos, show a message if (!todos.value.length) { return html`<p><em>You don't have any todos yet.</em></p>`; } // Otherwise, render the todo items return html` <ul> ${todos.value.map(function (todo, index) { return html`<li>${todo} <button data-delete="${index}">Delete</button></li>`; })} </ul>`; }
One little gotcha with the html()
method is that we need to run it anytime we're creating a new template literal. That includes inside the Array.prototype.map()
method's callback function.
We also need to omit the Array.prototype.join()
method. The html()
method handles converting everything into strings under-the-hood.
Rendering our template
Next, we'll pass our template and the element to render it to into the render()
function as arguments (this is the opposite order of the Reef component()
method).
// Create a component // Renders into the UI, and updates whenever the data changes render(html`<${getHTML} />`, app);
Preact wants the template you're passing in to be an element, so we need to wrap it in angled brackets with a self-closing slash (</>
). And because it's a template literal, we need to also pass it into the html()
method.
Yes, this is a weird quirk of Preact. And yes, I find it silly.
Running code when our state changes
With Reef, we can listen for reef:render
events and run code in response (like saving our todos
to localStorage
).
In Preact, we can do the same thing with the effect()
function. Let's first add that to our import
.
import {html, render, signal, effect} from 'https://cdn.jsdelivr.net/npm/preact-htm-signals-standalone/dist/standalone.js';
Then, we'll pass in a callback function that saves our items whenever our data is updated.
// Save todos whenever state is updated effect(function () { localStorage.setItem('todos-preact', JSON.stringify(todos.value)); });
Quirks of Preact
As far as "big and well known" libraries go, Preact is probably one of my favorite.
But to be honest, I still find Reef easier to work. It just works like vanilla JS, without the fuss.
By comparison, Preact…
- Requires every template literal to get passed into the
html()
method - Requires elements without a closing tag to use a self-closing slash, even if the normal HTML element doesn't require one (ex.
<input>
will error, but<input />
will not) - Signals force you to set the main object and use destructuring instead of listening for property changes
- Can't be loaded is a global object unless you compile it into an IIFE with your own build step
None of these are deal-breakers for me, but they are examples of how tools often introduce new problems when solving existing ones.
Tomorrow, I'll show you a trick for more easily managing state with Preact.
The Vanilla JS Academy is a project-based online JavaScript workshop for beginners. Click here to learn more.
Cheers,
Chris
Want to share this with others or read it later? View it in a browser.
0 Komentar untuk "[Go Make Things] A mostly vanilla JS way to use Preact"