È uscito il Corso Java Completo
Torna al blog

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.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

9 min di lettura

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

ReactSolidJS
Esecuzione del componenteA ogni aggiornamentoUna volta sola
Aggiornamento del DOMConfronto col DOM virtualeDiretto sul nodo
Dipendenze degli effettiLe dichiari tuRilevate automaticamente
Memoizzazione manualeSpesso necessariaNon prevista
Lettura dello statocountcount()
Liste.map()<For>
Peso del runtimeCirca 45 KBCirca 7 KB
Ecosistema e lavoroEnormePiccolo

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.