Vue 3 Pinia vs Vuex: Gestione dello Stato Moderna e Domande da Colloquio 2026

Pinia vs Vuex a confronto: design delle API, supporto TypeScript, performance, strategie di migrazione e domande frequenti sulla gestione dello stato Vue nei colloqui 2026.

Diagramma di confronto tra Vue 3 Pinia e Vuex per la gestione dello stato

Pinia vs Vuex rappresenta il cambiamento più significativo nella gestione dello stato di Vue dalla pubblicazione di Vue 3. Con Pinia 3 che è ora la raccomandazione ufficiale e Vuex in modalità di manutenzione, comprendere le differenze tra queste due librerie è essenziale per gli sviluppatori Vue — e un argomento ricorrente nei colloqui tecnici.

Punto Chiave

Pinia è la soluzione ufficiale per la gestione dello stato in Vue 3. Vuex 5 è stato cancellato, ed Evan You ha definito Pinia come il de facto Vuex 5. Tutti i nuovi progetti Vue 3 dovrebbero utilizzare Pinia 3.

Pinia vs Vuex: Differenze Architetturali Fondamentali

La differenza architetturale fondamentale tra Pinia e Vuex risiede nel modo in cui avvengono le mutazioni dello stato. Vuex impone un flusso di dati unidirezionale rigoroso: i componenti invocano actions, le actions eseguono commit di mutations, e le mutations modificano lo stato. Pinia elimina completamente il livello delle mutation, permettendo modifiche dirette dello stato dalle actions o persino dai componenti.

Questa semplificazione riduce il codice boilerplate di circa il 40% nella maggior parte delle codebase. Dove Vuex richiede quattro concetti (state, getters, mutations, actions), Pinia ne utilizza tre (state, getters, actions).

| Caratteristica | Pinia 3 | Vuex 4 | |----------------|---------|--------| | Mutations | Nessuna (modifiche dirette dello stato) | Richieste per le modifiche dello stato | | TypeScript | Inferenza completa, nessuna augmentation | Augmentation manuale dei tipi necessaria | | Architettura store | Molteplici store piatti | Singolo store con moduli annidati | | Composition API | Supporto nativo | Basato su Options API | | Dimensione bundle | ~1 KB gzipped | ~6 KB gzipped | | Vue Devtools | Supporto completo (v7) | Supporto completo | | SSR | Integrato | Richiede configurazione | | Hot Module Replacement | Integrato | Setup manuale |

Definire gli Store: Options API vs Sintassi Setup

Pinia offre due sintassi per la definizione degli store. La sintassi Options rispecchia la struttura di Vuex, facilitando la migrazione. La sintassi Setup sfrutta la Composition API di Vue 3 per la massima flessibilità.

stores/counter-options.tstypescript
import { defineStore } from 'pinia'

// Sintassi Options Store — familiare per gli sviluppatori Vuex
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    lastUpdated: null as Date | null,
  }),
  getters: {
    // I getters ricevono state come primo argomento con inferenza completa dei tipi
    doubleCount: (state) => state.count * 2,
    isPositive(): boolean {
      // Accesso ad altri getters tramite `this`
      return this.count > 0
    },
  },
  actions: {
    increment() {
      // Mutazione diretta dello stato — nessun commit() necessario
      this.count++
      this.lastUpdated = new Date()
    },
    async fetchCount(id: string) {
      // Le actions asincrone funzionano senza configurazione aggiuntiva
      const response = await fetch(`/api/counters/${id}`)
      const data = await response.json()
      this.count = data.count
    },
  },
})

La sintassi Options mappa direttamente i concetti di Vuex. Lo state sostituisce lo state di Vuex, i getters rimangono getters, e le actions assorbono sia le actions che le mutations di Vuex.

stores/counter-setup.tstypescript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// Sintassi Setup Store — pattern identici alla Composition API
export const useCounterStore = defineStore('counter', () => {
  // ref() diventa state
  const count = ref(0)
  const lastUpdated = ref<Date | null>(null)

  // computed() diventa getters
  const doubleCount = computed(() => count.value * 2)
  const isPositive = computed(() => count.value > 0)

  // Le funzioni diventano actions
  function increment() {
    count.value++
    lastUpdated.value = new Date()
  }

  async function fetchCount(id: string) {
    const response = await fetch(`/api/counters/${id}`)
    const data = await response.json()
    count.value = data.count
  }

  // Tutti gli state, getters e actions devono essere restituiti
  return { count, lastUpdated, doubleCount, isPositive, increment, fetchCount }
})

La sintassi Setup offre piena flessibilità: watchers, riutilizzo di composable e logica condizionale funzionano naturalmente all'interno della definizione dello store. Il compromesso è che ogni proprietà reattiva e metodo deve essere esplicitamente restituito.

Integrazione TypeScript: Dove Pinia Supera Vuex

Il supporto TypeScript è l'area in cui Pinia vince in modo netto. Vuex 4 richiede augmentation manuale dei tipi attraverso dichiarazioni di moduli, e con moduli complessi annidati l'inferenza dei tipi diventa fragile. Pinia inferisce i tipi automaticamente dalla definizione dello store.

typescript
// Vuex 4 — augmentation manuale dei tipi richiesta
import { Store } from 'vuex'

declare module 'vuex' {
  interface State {
    counter: {
      count: number
      lastUpdated: Date | null
    }
  }
}

// L'accesso allo state richiede type assertions o helper personalizzati
const count = (store.state as { counter: { count: number } }).counter.count
typescript
// Pinia — inferenza completa dei tipi, zero configurazione
const counter = useCounterStore()

// TypeScript riconosce counter.count come number
// TypeScript riconosce counter.doubleCount come number
// TypeScript riconosce counter.increment() come void
// TypeScript riconosce counter.fetchCount() come Promise<void>
counter.increment()
console.log(counter.doubleCount)

Pinia supporta inoltre store generici per pattern riutilizzabili — una capacità che richiede un notevole boilerplate in Vuex.

Composition API e Integrazione dei Composable

I Setup store di Pinia accettano qualsiasi composable Vue, abilitando pattern potenti come utility VueUse condivise o stato dipendente dal router.

stores/search.tstypescript
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'
import { useRoute } from 'vue-router'

export const useSearchStore = defineStore('search', () => {
  const route = useRoute()
  const query = ref('')
  const results = ref<SearchResult[]>([])
  const isLoading = ref(false)

  // Composable VueUse utilizzato direttamente nello store
  const debouncedSearch = useDebounceFn(async (term: string) => {
    if (!term.trim()) {
      results.value = []
      return
    }
    isLoading.value = true
    try {
      const res = await fetch(`/api/search?q=${encodeURIComponent(term)}`)
      results.value = await res.json()
    } finally {
      isLoading.value = false
    }
  }, 300)

  // Il watcher sincronizza il parametro query dell'URL con lo stato dello store
  watch(() => route.query.q, (q) => {
    if (typeof q === 'string') {
      query.value = q
      debouncedSearch(q)
    }
  })

  function setQuery(term: string) {
    query.value = term
    debouncedSearch(term)
  }

  return { query, results, isLoading, setQuery }
})

Questo pattern è impossibile in Vuex senza soluzioni alternative. I moduli Vuex non possono utilizzare composable, watchers o hook del router all'interno della loro definizione. La logica dello store deve essere suddivisa tra il modulo Vuex e funzioni utility esterne.

Pronto a superare i tuoi colloqui su Vue.js / Nuxt.js?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Comunicazione tra Store e Riferimenti Incrociati

Vuex utilizza un singolo store radice con moduli namespaced. L'accesso allo stato tra moduli richiede i parametri rootState e rootGetters, verbosi e complessi. Gli store Pinia sono indipendenti per design, e la comunicazione tra store avviene tramite import diretti.

stores/cart.tstypescript
import { defineStore } from 'pinia'
import { useProductStore } from './products'
import { useAuthStore } from './auth'
import { ref, computed } from 'vue'

export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])

  const total = computed(() => {
    // Accesso a un altro store invocando il suo composable
    const productStore = useProductStore()
    return items.value.reduce((sum, item) => {
      const product = productStore.getById(item.productId)
      return sum + (product?.price ?? 0) * item.quantity
    }, 0)
  })

  async function checkout() {
    const auth = useAuthStore()
    if (!auth.isAuthenticated) {
      throw new Error('Autenticazione richiesta')
    }
    // Logica di checkout
  }

  return { items, total, checkout }
})

Questa architettura a store piatti evita gli alberi di moduli profondamente annidati che rendono le grandi applicazioni Vuex difficili da navigare e da refactorizzare.

Migrazione da Vuex 4 a Pinia 3

La migrazione da Vuex a Pinia può avvenire modulo per modulo. Entrambe le librerie possono coesistere durante il periodo di transizione. La strategia raccomandata: iniziare con i moduli foglia (store da cui nessun altro modulo dipende) e procedere verso l'interno.

Compatibilità di Migrazione

Pinia e Vuex possono funzionare fianco a fianco nella stessa applicazione. Installare Pinia accanto a Vuex, migrare un modulo alla volta e rimuovere Vuex solo dopo che tutti i moduli sono stati convertiti.

typescript
// Prima: modulo Vuex
// store/modules/user.ts
const userModule = {
  namespaced: true,
  state: () => ({
    profile: null as UserProfile | null,
    preferences: {} as UserPreferences,
  }),
  mutations: {
    SET_PROFILE(state, profile: UserProfile) {
      state.profile = profile
    },
    UPDATE_PREFERENCES(state, prefs: Partial<UserPreferences>) {
      state.preferences = { ...state.preferences, ...prefs }
    },
  },
  actions: {
    async fetchProfile({ commit }) {
      const profile = await api.getProfile()
      commit('SET_PROFILE', profile)
    },
    async updatePreferences({ commit }, prefs: Partial<UserPreferences>) {
      await api.updatePreferences(prefs)
      commit('UPDATE_PREFERENCES', prefs)
    },
  },
  getters: {
    isLoggedIn: (state) => state.profile !== null,
    displayName: (state) => state.profile?.name ?? 'Guest',
  },
}
typescript
// Dopo: store Pinia
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { api } from '@/lib/api'

export const useUserStore = defineStore('user', () => {
  const profile = ref<UserProfile | null>(null)
  const preferences = ref<UserPreferences>({})

  const isLoggedIn = computed(() => profile.value !== null)
  const displayName = computed(() => profile.value?.name ?? 'Guest')

  async function fetchProfile() {
    profile.value = await api.getProfile()
  }

  async function updatePreferences(prefs: Partial<UserPreferences>) {
    await api.updatePreferences(prefs)
    Object.assign(preferences.value, prefs)
  }

  return { profile, preferences, isLoggedIn, displayName, fetchProfile, updatePreferences }
})

Il pattern di migrazione è coerente: le mutations si fondono in assegnazioni dirette all'interno delle actions, le chiamate commit() scompaiono, e gli helper mapState/mapGetters vengono sostituiti dalla destrutturazione del composable dello store.

SSR State Hydration con Pinia e Nuxt 4

Il Server-Side Rendering introduce complessità per la gestione dello stato. Pinia gestisce la serializzazione e l'hydration dello stato SSR automaticamente in Nuxt 4, mentre Vuex richiede una gestione manuale di window.__INITIAL_STATE__.

Insidia SSR

I Setup store che utilizzano composable come useRoute() o useFetch() necessitano di una gestione attenta nei contesti SSR. Questi composable devono essere invocati solo durante la fase di setup, mai all'interno di callback asincrone.

stores/products.ts — Store Pinia SSR-safe per Nuxt 4typescript
import { defineStore } from 'pinia'

export const useProductStore = defineStore('products', {
  state: () => ({
    items: [] as Product[],
    selectedCategory: 'all',
  }),
  actions: {
    async fetchProducts() {
      // useFetch è specifico di Nuxt — usare $fetch per le actions dello store
      this.items = await $fetch<Product[]>('/api/products', {
        query: { category: this.selectedCategory },
      })
    },
  },
  getters: {
    getById: (state) => (id: string) =>
      state.items.find((item) => item.id === id),
    filteredCount: (state) =>
      state.items.filter((p) => p.inStock).length,
  },
})

Nuxt 4 serializza automaticamente lo stato Pinia sul server e lo idrata sul client. Nessuna logica manuale di replaceState() o disidratazione necessaria.

Confronto delle Performance e Impatto sul Bundle

Il footprint di Pinia di ~1 KB gzipped è circa sei volte più piccolo dei ~6 KB di Vuex. Nelle applicazioni con code splitting, gli store Pinia sono tree-shakable: gli store non utilizzati e le loro dipendenze vengono esclusi dai bundle di produzione.

Le differenze di performance a runtime sono trascurabili per la maggior parte delle applicazioni. Entrambe le librerie utilizzano il sistema di reattività di Vue sotto il cofano. Il guadagno pratico in termini di performance deriva dal ridotto boilerplate che porta a meno re-render causati da subscription allo stato troppo ampie — il modello di store piatti di Pinia incoraggia store granulari, il che significa che i componenti sottoscrivono meno stato.

Con l'introduzione del Vapor Mode in Vue 3.6, sia Pinia che Vuex beneficiano della pipeline di rendering più veloce. Tuttavia, la più stretta integrazione di Pinia con la Composition API lo posiziona meglio per le ottimizzazioni del Vapor Mode.

Domande Frequenti sui Colloqui sulla Gestione dello Stato Vue

I colloqui tecnici testano frequentemente le conoscenze sulla gestione dello stato. Ecco le domande più comuni nei colloqui per sviluppatori Vue nel 2026.

D: Perché il livello mutation di Vuex è stato rimosso in Pinia?

Le mutations esistevano in Vuex per abilitare il debugging time-travel nei DevTools. Ogni modifica dello stato doveva passare attraverso una mutation sincrona affinché i DevTools potessero registrare snapshot. Pinia ottiene la stessa capacità di debugging tracciando le modifiche dello stato a livello reattivo, rendendo il livello esplicito delle mutation superfluo. Il risultato: meno boilerplate, stesse capacità di debugging.

D: Quando si dovrebbe preferire un Setup store a un Options store?

I Setup store sono la scelta migliore quando lo store necessita di composable (useRoute, useDebounceFn), watchers complessi o logica condivisa da funzioni composable esterne. Gli Options store funzionano bene per stato CRUD semplice con getters basilari. In pratica, i Setup store sono più comuni nelle codebase di produzione perché rispecchiano i pattern della Composition API nei componenti.

D: Come gestisce Pinia la reattività per gli oggetti annidati?

Pinia utilizza reactive() di Vue per l'intero oggetto stato e ref() per le singole proprietà nei Setup store. La reattività profonda si applica per default — le mutazioni degli oggetti annidati vengono tracciate automaticamente. Per casi sensibili alle performance con grandi dataset, shallowRef() può disattivare la reattività profonda.

D: Spiegare le dipendenze tra store in Pinia vs Vuex.

In Vuex, l'accesso tra moduli utilizza i parametri rootState e rootGetters nelle actions, creando un accoppiamento implicito. In Pinia, gli store si importano a vicenda direttamente tramite chiamate useOtherStore(). Questo rende le dipendenze esplicite e permette a TypeScript di verificarle a tempo di compilazione. Le dipendenze circolari funzionano in Pinia purché non vengano invocate durante la fase iniziale di setup.

D: Cosa succede allo stato Pinia durante l'hydration SSR?

Durante l'SSR, Pinia serializza tutti gli stati degli store attivi in JSON e incorpora il risultato nel payload HTML. Sul client, Pinia idrata ogni store prima che i componenti vengano montati. Qualsiasi stato impostato durante il rendering lato server è disponibile immediatamente sul client senza duplicare le chiamate API. In Nuxt 4, questo processo è completamente automatico.

Per ulteriore preparazione ai colloqui Vue e Nuxt, è possibile esplorare il modulo interview sulla gestione dello stato oppure consultare il modulo sui composable Vue.

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Conclusione

  • Pinia 3 è la libreria ufficiale per la gestione dello stato Vue — Vuex è in modalità di manutenzione senza una release v5 pianificata
  • Il livello mutation è scomparso: le actions di Pinia modificano lo stato direttamente, riducendo il boilerplate di ~40%
  • L'inferenza TypeScript funziona immediatamente in Pinia senza alcuna augmentation, a differenza delle dichiarazioni manuali dei tipi di Vuex
  • I Setup store si integrano perfettamente con la Composition API di Vue 3, abilitando il riutilizzo dei composable e i watchers all'interno degli store
  • L'architettura a store piatti sostituisce l'albero di moduli annidati di Vuex, rendendo la comunicazione tra store esplicita e type-safe
  • La migrazione può avvenire in modo incrementale: Pinia e Vuex coesistono, permettendo una conversione modulo per modulo
  • L'hydration SSR è automatica in Nuxt 4 con Pinia — nessuna serializzazione manuale necessaria
  • Per tutti i nuovi progetti Vue 3 nel 2026, Pinia è la scelta chiara con una migliore developer experience, bundle più piccoli e un supporto ecosistemico più forte

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Tag

#vue
#pinia
#vuex
#state-management
#interview

Condividi

Articoli correlati