SolidJS è una libreria JavaScript moderna per la creazione di UI/UX, creata da Ryan Carniato e rilasciata nel 2020, che adotta un approccio reattivo alla creazione di interfacce utente, il che significa che queste si aggiornano automaticamente in risposta ai cambiamenti nei dati o nello stato dei singoli componenti dell’applicazione.
Alcune delle caratteristiche fondamentali di SolidJS includono:
- Una API leggera e una dimensione del bundle finale contenuta, che lo rendono facile da usare e molto veloce
- Supporto per il rendering lato server (SSR), che può migliorare le prestazioni e l’ottimizzazione per i motori di ricerca (SEO)
- Una sintassi semplice e intuitiva, con un focus sulla programmazione dichiarativa
- Un motore di rendering efficiente che minimizza gli aggiornamenti non necessari al DOM
Naturalmente, SolidJS viene spesso confrontato con altre librerie/framework JavaScript popolari come React, Vue e Angular. Questa libreria è quindi diventata popolare tra gli sviluppatori che cercano un’alternativa leggera e moderna rispetto a queste opzioni più consolidate.
SolidJS vs React: perché?
Tuttavia, ci sono alcune differenze e vantaggi notevoli nell’usare SolidJS rispetto a React e ad altri framework JavaScript. Innanzitutto: perché viene comparato a React?
SolidJS viene confrontato con React perché entrambi sono progettati per creare componenti UI e gestire lo stato delle applicazioni web.
Se hai già sviluppato in precedenza con React e i suoi “componenti funzionali“, SolidJS ti sembrerà molto naturale perché segue la stessa filosofia, con flusso di dati unidirezionale, segregazione di lettura/scrittura e interfacce immutabili.
SolidJS e React condividono concetti simili come l’architettura basata sui componenti e i paradigmi di programmazione reattiva. Tuttavia, il modo in cui gestiscono la reattività e gli aggiornamenti è diverso. React si basa su un DOM virtuale (“Virtual DOM”) e un algoritmo di differenziazione per aggiornare l’interfaccia utente, mentre SolidJS utilizza una reattività granulare e compila i componenti in codice altamente ottimizzato con un overhead minimo.
Poiché condividono molto della stessa filosofia e entrambi fanno uso di JSX, la sintassi del loro codice è molto simile. Ad esempio, creeremo un semplice componente UserInfo che richiede tre proprietà (username, role e points) e restituisce un output in base al ruolo dell’utente.
Questo è il codice scritto in React:
import React from "react";
function UserInfo(props) {
const { username, role, points } = props;
return (
<div>
<p>Welcome {username}! You currently have {points * 1000} points!</p>
{role === "admin" && <div>Admin Panel</div>}
{role === "guest" && <div>Guest Panel</div>}
</div>
);
}
export default UserInfo;
Code language: JavaScript (javascript)
In questo componente, effettuiamo la destrutturazione delle proprietà username, role e points dall’oggetto props. Quindi, utilizziamo un template literal per produrre il messaggio di benvenuto con i valori dell’username e dei punti, e infine utilizziamo due istruzioni di rendering condizionale con l’operatore && per visualizzare il pannello amministratore o ospite in base al valore della proprietà role.
Quello seguente è invece lo stesso componente scritto in TypeScript e SolidJS:
import { JSX, createSignal } from "solid-js";
interface Props {
username: string;
role: string;
points: number;
}
export default function UserInfo(props: Props): JSX.Element {
const { username, role, points } = props;
const isAdmin = role === "admin";
const isGuest = role === "guest";
const pointsFormatted = points * 1000;
return (
<div>
<p>Welcome {username}! You currently have {pointsFormatted} points!</p>
{isAdmin && <div>Admin Panel</div>}
{isGuest && <div>Guest Panel</div>}
</div>
);
}
Code language: JavaScript (javascript)
Come abbiamo detto, con SolidJS possiamo utilizzare JSX proprio come con React, ma non è obbligatorio. SolidJS fornisce una sintassi alternativa a JSX che funge da template engine (“template syntax”), che è simile alla sintassi comune HTML, ma potenziata. Questa sintassi alternativa può essere utilizzata al posto di JSX nei componenti SolidJS.
I componenti SolidJS possono inoltre essere scritti interamente in TypeScript, senza alcun utilizzo di JSX o sintassi alternativa, se lo si desidera. Tuttavia, l’utilizzo di JSX o della template syntax nei componenti SolidJS è estremamente utile a rendere il codice più leggibile e facile da comprendere.
Ecco il componente SolidJS precedente riscritto utilizzando la template syntax:
import { createSignal } from "solid-js";
interface Props {
username: string;
role: string;
points: number;
}
export default function UserInfo(props: Props) {
const { username, role, points } = props;
const isAdmin = role === "admin";
const isGuest = role === "guest";
const pointsFormatted = points * 1000;
return `
<div>
<p>Welcome ${username}! You currently have ${pointsFormatted} points!</p>
${isAdmin ? '<div>Admin Panel</div>' : ''}
${isGuest ? '<div>Guest Panel</div>' : ''}
</div>
`;
}
Code language: JavaScript (javascript)
In questo caso abbiamo utilizzato i backticks per definire le stringhe che rappresentano la struttura HTML del componente. Abbiamo utilizzato la comune sintassi di Javascript ${} per l’interpolazione dinamica di dati ed espressioni nell’HTML e gli operatori ternari per produrre condizionatamente gli elementi HTML all’interno del template.
Perché SolidJS è più di una semplice alternativa a React e Svelte?
Innanzitutto, abbiamo la reattività granulare menzionata in precedenza. SolidJS utilizza un sistema reattivo fortemente granulare che tiene traccia delle singole modifiche nello stato dell’applicazione, portando a un aggiornamento efficiente e veloce.
Inoltre, offre elevate prestazioni a tempo di esecuzione avendo a disposizione un compilatore dedicato che ottimizza il codice durante la fase di compilazione, producendo componenti più piccoli e veloci con un minimo overhead di run-time.
Con SolidJS non abbiamo nessun Virtual DOM. SolidJS elimina infatti la necessità di un Virtual DOM utilizzando apposite strategie di aggiornamento dinamico e rendering a tempo di compilazione, riducendo così l’utilizzo della memoria e migliorando ulteriormente le prestazioni.
La reattività è una caratteristica chiave di SolidJS, grazie al quale il framework tiene traccia delle dipendenze a un livello molto granulare, fino alle singole proprietà o espressioni utilizzate in un componente. Ciò consente a SolidJS di aggiornare solo le parti dell’interfaccia utente che effettivamente devono cambiare, anziché renderizzare l’intero componente ogni volta che c’è una modifica.
SolidJS: programmazione reattiva
Questo potente sistema è ottenuto attraverso una tecnica chiamata “programmazione reattiva”.
In SolidJS, la programmazione reattiva è integrata nel cuore del framework ed è utilizzata per aggiornare automaticamente l’interfaccia utente quando i dati cambiano. Ciò significa che gli sviluppatori non devono scrivere manualmente il codice per aggiornare l’interfaccia utente in risposta alle modifiche dei dati.
I vantaggi prestazionali dello specifico sistema informatico di reattività implementato in SolidJS sono significativi, consentendo aggiornamenti più veloci ed efficienti all’intera UI, con conseguente esperienza utente più fluida e performante. Inoltre, questo sistema consente la produzione di codice più manutenibile e scalabile, poiché riduce la quantità di istruzioni necessaria per gestire gli aggiornamenti dell’interfaccia utente.
I benefici prestazionali della reattività granulare di SolidJS sono evidenziati dai benchmark e dalle applicazioni del mondo reale. Il team di sviluppo di SolidJS afferma che il suo motore di reattività è nettamente più veloce di altri framework popolari come React e Vue, soprattutto negli scenari in cui i componenti devono elaborare molti dati o logiche complesse.
Tuttavia, il team di SolidJS sottolinea anche che le prestazioni non sono l’unico vantaggio del suo approccio di programmazione reattiva. Aggiornando automaticamente l’interfaccia utente in risposta alle modifiche, SolidJS rende lo sviluppo di UI più veloce e semplice, e la quantità di codice utile alla gestione dello stato e agli aggiornamenti dell’interfaccia utente e ai relativi dettagli si riduce. Questo favorisce un’esperienza di sviluppo semplice e intuitiva, che può essere particolarmente utile per applicazioni complesse o di grandi dimensioni.
Possiamo quindi affermare che il sistema di reattività di SolidJS porta vantaggi sia a livello di performance complessiva dell’applicazione sia a livello di qualità del codice.
Proprio nel contesto delle performance SolidJS è spesso paragonato con Svelte, un’altra libreria Javascript popolare e incredibilmente veloce.
Velocità e performance
SolidJS e Svelte sono infatti considerati simili in questo ambito essendo tra i framework JavaScript più veloci, dato che entrambi utilizzano un approccio di programmazione reattiva e una tecnica di ottimizzazione a tempo di compilazione per ottenere alte prestazioni e un overhead minimo.
Sia SolidJS che Svelte implementano strategie di rendering a tempo di compilazione per minimizzare la dimensione del codice finale dell’applicazione e aggiornare “chirurgicamente” la UI in modo imperativo senza necessità di tecniche aggiuntive come il Virtual DOM. SolidJS utilizza un compilatore basato su TypeScript che analizza il codice in fase di compilazione e genera codice JavaScript ottimizzato, rimuovendo il codice non necessario e migliorando le prestazioni. Svelte, invece, utilizza un compilatore proprietario che analizza il codice e genera codice JavaScript vanilla altamente ottimizzato.
A differenza di quest’ultimo SolidJS non utilizza un compilatore JavaScript separato, ma sfrutta direttamente l’ambiente di run-time JavaScript per interpretare ed eseguire il proprio codice.
Framework di run-time
SolidJS è un framework di run-time, il che significa che il suo codice viene eseguito a tempo di esecuzione dal motore JavaScript, nel browser o sul server (ad esempio, utilizzando Node.js).
Il framework fornisce un nutrito insieme di API e strumenti che gli sviluppatori possono utilizzare per costruire componenti e applicazioni reattive utilizzando una sintassi dichiarativa, e Il suo codice può essere scritto sia in TypeScript o direttamente JavaScript.
Quando viene eseguita un’applicazione SolidJS, il suo motore di run-time elabora i componenti e crea una rappresentazione virtuale dell’interfaccia utente. Il run-time di SolidJS utilizza quindi questa rappresentazione virtuale per aggiornare efficientemente il vero DOM durante le modifiche allo stato dell’applicazione.
Infine, SolidJS fornisce un supporto “out-of-the-box” per il rendering lato server (SSR), che va a migliorare drasticamente il tempo di caricamento iniziale dell’applicazione ottimizzando di conseguenza il SEO, a differenza del rendering lato client che “inietta” il codice JavaScript prodotto nel DOM della pagina.
I segnali (signals): molto più che semplici variabili di stato
In SolidJS, i segnali (signals) sono un concetto fondamentale utilizzato per gestire lo stato dell’applicazione. I segnali sono simili alle variabili di stato in altri framework come React, ma sono più flessibili e possono rappresentare una vasta gamma di tipi di dati, tra cui oggetti, array e funzioni.
Un segnale è essenzialmente un valore reattivo che può essere letto e aggiornato da un componente SolidJS. Quando un segnale viene aggiornato, SolidJS attiva automaticamente una nuova renderizzazione del componente per riflettere il valore aggiornato.
In SolidJS, i segnali vengono creati utilizzando la funzione createSignal. Questa funzione prende un valore iniziale come argomento e restituisce una tupla che contiene due elementi: il valore del segnale stesso e una funzione che può essere utilizzata per aggiornarlo.
Ecco un esempio:
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
function handleClick() {
setCount(count() + 1);
}
return (
<div>
<p>Count: {count()}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
Code language: JavaScript (javascript)
In questo esempio, creiamo un segnale chiamato count utilizzando createSignal e lo inizializziamo a 0. Successivamente, definiamo una funzione handleClick che aggiorna il segnale count chiamando setCount con il valore corrente di count più uno. Infine, renderizziamo il valore di count e un pulsante che attiva la funzione handleClick.
Come puoi vedere nel codice, abbiamo aggiunto le parentesi quando utilizziamo le variabili di stato, come count() invece di count.
Questo perché SolidJS utilizza la programmazione reattiva e la lazy evaluation per aggiornare efficientemente l’interfaccia utente. Quando usiamo una variabile di stato in un componente SolidJS, in realtà stiamo creando un segnale e leggendone il valore corrente chiamandolo come una funzione. Questo approccio consente a SolidJS di tenere traccia delle dipendenze e aggiornare l’interfaccia utente solo quando necessario, senza la necessità di aggiornamenti o re-render manuali.
Quando vogliamo leggere il valore corrente di una variabile di stato in un componente SolidJS, dobbiamo quindi chiamarlo come una funzione utilizzando le parentesi ().
Altri concetti di SolidJS
Altri potenti concetti di SolidJS che forniscono una serie di strumenti e tecniche per la creazione di applicazioni veloci, scalabili e modulari sono i seguenti:
- Fragment, che consente di raggruppare una lista di elementi senza introdurre un ulteriore elemento contenitore. Questo può essere utile quando non si desidera aggiungere un ulteriore wrapper alla struttura ad albero del componente.
- Portals, che forniscono un modo per renderizzare un componente figlio in una posizione diversa nella gerarchia DOM rispetto al componente padre. Ciò può essere utile quando è necessario renderizzare un componente al di fuori del componente padre per motivi di stile o di z-index.
- Context, che consente di passare i dati lungo la gerarchia dei componenti senza la necessità di passare le props (proprietà) attraverso componenti intermedi. Utile quando si ha una gerarchia composta da molti componenti annidati che hanno necessità di accedere agli stessi dati.
- Suspense, una tecnica per dilazionare il rendering di un componente fino a quando i suoi dati non sono stati recuperati o caricati. Questo contribuisce a migliorare le prestazioni reali e percepite dell’applicazione mostrando un indicatore di caricamento mentre i dati vengono recuperati.
- Error boundaries, che consentono di gestire elegantemente gli errori che si verificano all’interno di un componente. Fondamentali per prevenire il crash dell’intera applicazione a causa di un errore in un singolo componente.
- Lazy components, che consentono di posticipare il caricamento di un componente fino a quando non è effettivamente necessario, migliorando di fatto il tempo di caricamento iniziali di un’applicazione richiedendo solo i componenti immediatamente necessari.
- Rendering asincrono e concorrente, per renderizzare i componenti in parallelo, migliorando ulteriormente le prestazioni complessive dell’applicazione. Queste tecniche sono particolarmente utili per i componenti complessi o di grandi dimensioni che richiedono molto tempo per essere renderizzati.
- Delega implicita, consente di definire un insieme di proprietà o metodi su un componente genitore che possono essere delegati automaticamente ai componenti figli, riducendo il codice boilerplate e rendendo la struttura stessa dei componenti più modulare.
- Rendering lato server (SSR) e idratazione consentono di renderizzare un’applicazione SolidJS sul server e poi “idratarla” sul client. Questo, come detto precedentemente, migliora il tempo di caricamento iniziale di un’applicazione e l’indicizzazione sui motori di ricerca (SEO).
- Direttive, che forniscono un modo per associare “comportamenti” agli elementi HTML senza la necessità di scrivere componenti dedicati e personalizzati. In questo modo è possibile aggiungere azioni e comportamenti dinamici agli elementi HTML esistenti.
- Lo Streaming, che consente al server di inviare i dati al client non appena sono disponibili, senza dover aspettare che l’intera risposta venga generata, contribuendo a migliorare le prestazioni reali e percepite dell’applicazione e riducendo il tempo di caricamento iniziale.