Stato persistente in Vue 3, senza scomodare Pinia
Tema, filtri, bozze, carrelli: la maggior parte dei problemi di 'state management' sono in realtà problemi di persistenza. Come far sopravvivere lo stato Vue a reload e cambi di tab — a mano, e in una riga.
Ogni app Vue prima o poi accumula una manciata di valori che devono sopravvivere al reload della pagina: il tema scelto, un filtro di ricerca, un form scritto a metà, un carrello. E ogni team, prima o poi, fa la stessa riunione: "aggiungiamo Pinia per questa roba?"
Di solito, no. Uno store risolve lo stato condiviso; questi sono valori persistenti — e incollare la persistenza a uno store ti costringe comunque a scrivere la stessa idraulica. Guardiamola da vicino, quell'idraulica, perché i bug abitano nei dettagli.
La versione fatta a mano (e le sue trappole)
L'approccio ingenuo sta in sei righe:
import { ref, watch } from 'vue';
const theme = ref(JSON.parse(localStorage.getItem('theme') ?? '"light"'));
watch(theme, (value) => localStorage.setItem('theme', JSON.stringify(value)));
Funziona — sulla tua macchina, oggi. In produzione ha almeno cinque falle:
- In SSR esplode. Su Nuxt o su qualsiasi render server,
localStoragenon esiste. Primo errore di hydration dello sprint. - I dati corrotti lanciano eccezioni. Basta un valore malformato nello storage (una release vecchia, un'estensione, un utente curioso) e
JSON.parsetira giù il componente. - Le tab non si parlano. Cambi il tema in una tab e le altre cinque tengono quello vecchio fino al reload. L'evento
storagerisolve — se ti ricordi di usarlo, e di rimuovere il listener, altrimenti hai costruito un memory leak. - I valori stantii vivono per sempre. Un token in cache, una bozza del mese scorso: senza una scadenza, lo storage diventa un cassetto del disordine di cui l'app si fida ciecamente.
- Tempeste di scritture. Persistere un campo di testo a ogni tasto premuto martella un'API sincrona sul main thread.
Nessuno di questi problemi è difficile. Tutti insieme, rifatti in ogni componente che ne ha bisogno, sono esattamente il tipo di codice che in una codebase si moltiplica in cinque implementazioni leggermente diverse — faccio audit di front-end per mestiere, e ti garantisco che succede.
La versione in una riga
È l'esigenza da cui nasce vue-smart-state, un piccolo composable open source che mantengo io (~2 kB, zero dipendenze — piena trasparenza: il pacchetto è mio):
import { useState } from 'vue-smart-state';
const [theme, setTheme] = useState<'light' | 'dark'>('light', {
persist: true,
storageKey: 'theme',
syncTabs: true,
});
Una chiamata copre l'intera checklist: ripristino al caricamento, persistenza a ogni modifica, sync tra tab con pulizia corretta dei listener, sicurezza in SSR e una gestione errori che degrada invece di lanciare. L'API ricalca volutamente lo useState di React — tupla più updater funzionale (setTheme(t => …)) — perché metà degli sviluppatori che entrano in un team Vue si portano dietro quella memoria muscolare.
I dettagli da produzione sono a portata di opzione:
// Una sessione che scade da sola
const [token, setToken] = useState('', {
persist: true,
storageKey: 'token',
ttl: 15 * 60 * 1000,
});
// Una bozza che non martella lo storage a ogni tasto
const [draft, setDraft] = useState('', {
persist: true,
storageKey: 'draft',
writeDebounce: 300,
});
E per Nuxt c'è un modulo da una riga, nuxt-smart-state, che auto-importa il composable come useSmartState — con un alias scelto di proposito, visto che lo useState di Nuxt risolve un problema diverso (stato server per richiesta, non persistenza nel browser).
Quando lo store lo vuoi davvero
Angolo dell'onestà: se molti componenti lontani mutano lo stesso stato complesso, se ti serve il time-travel dei devtools, o se al team giova un unico pattern canonico per i dati globali — usa Pinia, è eccellente. Il punto non è "gli store sono cattivi"; è che la persistenza non è un motivo per adottarne uno. Scegli lo store per condividere, un composable di persistenza per persistere, e lascia che ognuno faccia il suo mestiere.
La lezione più ampia è una di quelle che ripeto nel lavoro di architettura: la maggior parte delle discussioni "ci serve una soluzione di state management" evapora appena dai un nome al requisito vero. Condividere, persistere, sincronizzare e fare cache sono quattro problemi diversi con la stessa maglietta addosso.
Sono un software architect indipendente con focus sul front-end — e ogni tanto impacchetto bene le parti noiose, come il composable qui sopra. Se la tua codebase Vue ha cinque modi di fare la stessa cosa, parliamone: una call conoscitiva di 30 minuti è gratuita.