Cos'è SolidJS e come funziona
Cos'è SolidJS e perché la sintassi somiglia a React ma il modello è opposto: i componenti girano una volta sola, i signal, e quando ha senso sceglierlo.
Apri un file SolidJS e vedi JSX, componenti che sembrano funzioni React, qualcosa che assomiglia a useState. Pensi di sapere già tutto, copi un pezzo di codice React dentro, e non funziona. Non è un bug: è che sotto una sintassi quasi identica c'è un modello opposto. In questo articolo trovi qual è la differenza vera, cosa sono i signal, perché si leggono come funzioni e quando SolidJS ha davvero senso.
Cos'è
SolidJS è una libreria per interfacce che usa JSX come React, ma esegue ogni componente una volta sola e collega direttamente i dati ai nodi del DOM, senza DOM virtuale e senza rieseguire nulla a ogni aggiornamento.
Tutto il resto — i signal, l'assenza di memoizzazione, le prestazioni — discende da questa frase.
I componenti girano una volta sola
Questo è il punto da cui parte tutto, e conviene vederlo con un esempio. Prendi il classico contatore in React:
function Contatore() {
const [count, setCount] = useState(0);
console.log("eseguito"); // stampa a OGNI click
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Ogni volta che premi il pulsante, React riesegue l'intera funzione Contatore. Ricrea le costanti, ricrea le funzioni di callback, produce una nuova descrizione della pagina e la confronta con la precedente per capire cosa cambiare.
La stessa cosa in Solid:
function Contatore() {
const [count, setCount] = createSignal(0);
console.log("eseguito"); // stampa UNA VOLTA, al montaggio
return <button onClick={() => setCount(count() + 1)}>{count()}</button>;
}
Quel console.log stampa una volta sola, per sempre. Il corpo della funzione è codice di impostazione: gira al montaggio, costruisce i nodi del DOM reali, collega count() a quel preciso nodo di testo e non viene più richiamato. Quando il valore cambia, Solid aggiorna direttamente quel nodo di testo. Non riesegue il componente, non confronta niente.
È lo stesso spostamento concettuale di Svelte — sapere in anticipo cosa dipende da cosa invece di scoprirlo confrontando — ma ottenuto in un modo diverso: Svelte lo deduce in compilazione analizzando il codice, Solid lo scopre a runtime tracciando chi legge cosa.
I signal: la primitiva reattiva
Un signal è un contenitore di valore che sa chi lo sta leggendo.
import { createSignal, createEffect, createMemo } from "solid-js";
const [nome, setNome] = createSignal("Edoardo");
console.log(nome()); // "Edoardo" — si legge chiamandolo
setNome("Marta"); // chi leggeva nome() viene ricalcolato
Il meccanismo è semplice e vale la pena capirlo perché spiega tutto il resto. Quando Solid esegue un pezzo di codice reattivo — un effetto, un calcolo derivato, un'espressione nel JSX — tiene nota di quale codice sta girando. Ogni signal letto in quel momento registra quel pezzo di codice tra i propri iscritti. Quando il signal cambia, riesegue solo quelli.
Nessuno deve dichiarare le dipendenze: le dipendenze si scoprono da sole, nel momento in cui leggi il valore.
createEffect
Un effetto è codice che si riesegue quando cambia un signal che ha letto.
const [query, setQuery] = createSignal("");
createEffect(() => {
console.log("cerco:", query());
});
Nessun array di dipendenze. Nessuna lista da tenere allineata a mano, nessun linter che ti avvisa che ne hai dimenticata una. Solid sa che l'effetto dipende da query perché lo ha visto leggerlo.
Se arrivi da React e hai passato pomeriggi a inseguire cicli infiniti di useEffect causati da un oggetto ricreato a ogni render, questa è la differenza che si sente di più.
createMemo
Serve quando un calcolo è costoso e viene letto in più punti.
const [prodotti, setProdotti] = createSignal([]);
const [filtro, setFiltro] = createSignal("");
const filtrati = createMemo(() =>
prodotti().filter((p) => p.nome.includes(filtro()))
);
filtrati() si ricalcola solo quando cambia prodotti o filtro, e il risultato viene condiviso tra tutti i punti che lo leggono. Importante: non è l'equivalente di useMemo come ottimizzazione difensiva. In React useMemo serve spesso a evitare che i figli si ridisegnino, e in Solid quel problema non esiste: qui createMemo serve solo se il calcolo in sé è pesante o se il valore viene letto molte volte.
Perché si scrive count() e non count
Questa è la trappola numero uno di chi arriva da React, e non è un capriccio sintattico.
Se count fosse un valore normale, nel momento in cui lo passi da qualche parte passeresti una fotografia: il numero in quel momento, scollegato dalla sorgente. Solid non riesegue mai il componente, quindi non avrebbe una seconda occasione per rileggerlo.
Essendo una funzione, invece, la lettura avviene nel punto e nel momento giusto — dentro l'effetto, dentro il nodo del JSX — ed è proprio quella chiamata a registrare l'iscrizione.
// NO: legge il valore qui, una volta, e lo congela
<Figlio valore={count()} />
// SÌ: passa il lettore, il figlio lo chiamerà quando serve
<Figlio valore={count} />
Lo stesso vale nel JSX:
// NO: la condizione viene valutata una volta sola
{loggato() ? <Dashboard /> : <Login />} // questo funziona, è dentro il JSX
// NO davvero: fuori dal JSX la reattività si perde
const vista = loggato() ? <Dashboard /> : <Login />;
return <div>{vista}</div>;
La regola pratica: la chiamata al signal deve stare dentro il JSX o dentro un contesto reattivo. Appena la estrai in una variabile normale, hai letto un valore e basta. È il tipo di errore che non dà nessun messaggio: semplicemente l'interfaccia non si aggiorna, e ci passi mezz'ora.
Le altre differenze pratiche da React
| React | SolidJS | |
|---|---|---|
| Esecuzione del componente | A ogni aggiornamento | Una volta sola |
| Aggiornamento del DOM | Confronto col DOM virtuale | Diretto sul nodo |
| Dipendenze degli effetti | Le dichiari tu | Rilevate automaticamente |
| Memoizzazione manuale | Spesso necessaria | Non prevista |
| Lettura dello stato | count | count() |
| Liste | .map() | <For> |
| Peso del runtime | Circa 45 KB | Circa 7 KB |
| Ecosistema e lavoro | Enorme | Piccolo |
Le liste si fanno con <For>, non con .map(). Il motivo è coerente col resto: .map() ricostruirebbe l'intero array di elementi a ogni cambiamento, e Solid vuole invece riusare i nodi esistenti e creare solo quelli nuovi.
<For each={prodotti()}>
{(prodotto) => <li>{prodotto.nome} — {prodotto.prezzo} €</li>}
</For>
Le proprietà non si destrutturano. function Card({ titolo }) sembra innocuo ma legge il valore nel momento del montaggio e lo congela, esattamente come prima. Si usa props.titolo dentro il JSX, oppure splitProps se devi separarle.
Lo stato annidato si gestisce con gli store, non con signal dentro signal:
import { createStore } from "solid-js/store";
const [carrello, setCarrello] = createStore({ articoli: [], totale: 0 });
setCarrello("articoli", (a) => [...a, nuovo]);
Prestazioni e peso
Qui SolidJS è oggettivamente ai primissimi posti di qualunque confronto, e le ragioni sono strutturali: non spedisce un motore di riconciliazione, non ricostruisce descrizioni della pagina, non confronta alberi. Quando un dato cambia, il lavoro svolto è proporzionale a quanti nodi dipendono davvero da quel dato — non alla dimensione del componente.
Ma vale la precisazione onesta che faccio ogni volta: il framework raramente è il collo di bottiglia di un sito lento. Immagini non ottimizzate, richieste di rete mal gestite e query lente pesano ordini di grandezza di più. Se il tuo problema sono i Core Web Vitals, prima leggi come velocizzare un sito, poi eventualmente cambia libreria.
Dove il vantaggio si sente davvero è su interfacce con aggiornamenti molto frequenti — dashboard di dati in tempo reale, editor, visualizzazioni interattive — e su dispositivi modesti, dove il costo di avviare ed eseguire JavaScript è la voce dominante.
Errori comuni che fanno perdere ore
Copiare codice React. Funziona per il 70% e si rompe silenziosamente sul restante 30%. Gli useEffect con array di dipendenze, le destrutturazioni delle props, i .map() sulle liste: tutto compila, niente si aggiorna.
Dimenticare le parentesi. {count} dentro il JSX stampa la funzione, non il numero. Solid non ti avvisa.
Destrutturare le props. Il caso più insidioso, perché sembra idiomatico e nella prima schermata funziona.
Usare createEffect per calcolare un valore. È lo stesso errore che si fa con useEffect in React: se un valore deriva da altri valori, è un createMemo o una semplice funzione, non uno stato aggiornato da un effetto.
Aspettarsi che le librerie React funzionino. Non funzionano. L'interoperabilità è nulla: servono equivalenti Solid, e spesso esistono ma sono meno maturi.
Quando ha senso e quando no
La parte onesta, senza entusiasmo.
L'ecosistema è piccolo. SolidStart è il meta-framework ufficiale e fa il suo mestiere, ma componenti pronti, librerie di grafici, integrazioni con servizi esterni: per React esiste tutto in cinque versioni, per Solid a volte esiste una cosa mantenuta da una persona.
Le offerte di lavoro si contano sulle dita. In Italia, praticamente zero. Se stai imparando per lavorare, la risposta è nel quadro completo di React, Vue o Angular.
La somiglianza con React è un'arma a doppio taglio. Abbassa la barriera d'ingresso, ma crea l'illusione di sapere già come funziona. Chi lo prende sul serio deve disimparare qualcosa, e chi non lo fa scrive codice che sembra giusto e non è reattivo.
Quando ha senso davvero: progetti personali e MVP dove decidi tu, widget interattivi da incastrare in pagine esistenti, interfacce con aggiornamenti molto frequenti, contesti embedded o dispositivi limitati dove ogni kilobyte conta, e come strumento per capire meglio la reattività — dopo Solid, il modello di React ti è molto più chiaro.
Quando no: un'applicazione aziendale da mantenere per anni con un team che ruota, un progetto che dipende da librerie di terze parti specifiche, o qualunque cosa tu debba imparare per essere assunto.
In sintesi
SolidJS usa JSX come React ma il modello sotto è opposto: i componenti girano una volta sola, non a ogni aggiornamento.
Niente DOM virtuale, niente dipendenze da dichiarare, niente memoizzazione manuale. I signal tracciano da soli chi li legge, e il DOM viene aggiornato al nodo esatto.
I signal si leggono come funzioni — count(), non count — e non è un dettaglio: è ciò che permette alla lettura di avvenire nel punto reattivo giusto. È la trappola principale di chi arriva da React.
Prestazioni eccellenti e peso minimo, ma il framework raramente è il vero collo di bottiglia di un sito lento.
Il limite non è tecnico ma di contorno: ecosistema piccolo, quasi nessuna offerta di lavoro, e una somiglianza con React che inganna — copiare codice React non funziona.
Per inquadrare la scelta nel progetto intero, vedi come scegliere lo stack per una web app e quale framework scegliere.