Introduzione a Vue.js

vue.js

Vue.js è un framework che sta guadagnando sempre più popolarità nel mondo dello sviluppo web. Cerchiamo di capire cos’è, come funziona e perché potrebbe essere la scelta giusta per i vostri progetti.

Introduzione

Vue.js è un framework JavaScript progressivo utilizzato per costruire interfacce utente e applicazioni single-page. È stato creato da Evan You, un ex dipendente di Google che lavorava su AngularJS. La prima versione di Vue è stata rilasciata nel 2014, e da allora ha visto una crescita costante nella comunità degli sviluppatori.

Perché scegliere Vue.js? Beh, ci sono diversi motivi. Innanzitutto è molto leggero, con un peso di circa 20KB quando compresso. Poi è reattivo, nel senso che reagisce automaticamente ai cambiamenti dei dati aggiornando il DOM. È anche incredibilmente flessibile e può essere utilizzato sia per piccole parti di un’applicazione esistente sia per costruire intere applicazioni complesse. E forse la cosa più importante, è relativamente facile da imparare rispetto ad altri framework.

Confrontando Vue con React e Angular, vediamo che ognuno ha i suoi pro e contro. React è molto popolare e ha un ecosistema enorme, ma la sua libertà può diventare un problema per i principianti che potrebbero sentirsi sopraffatti dalle troppe scelte. Angular è completo e robusto, ma ha una curva di apprendimento ripida e può sembrare eccessivo per progetti più piccoli. Vue cerca di bilanciare questi due approcci, offrendo sia la flessibilità che la struttura, a seconda delle esigenze.

1. Concetti Fondamentali di Vue.js

1.1 L’Approccio Reattivo

La reattività è al centro di Vue. In pratica, quando i dati cambiano, la vista si aggiorna automaticamente. In Vue 2, questo era ottenuto utilizzando Object.defineProperty() per intercettare le operazioni di lettura e scrittura sulle proprietà degli oggetti. In Vue 3, è stato adottato un approccio più moderno utilizzando i Proxy di JavaScript, che offrono una migliore performance e coprono più casi d’uso.

Per esempio, in Vue 3 possiamo creare dati reattivi con reactive() o ref():

import { reactive, ref } from 'vue'

// Con reactive
const state = reactive({
  count: 0,
  message: 'Ciao'
})

// Con ref
const count = ref(0)
const message = ref('Ciao')

Quando modifichi state.count o count.value, Vue aggiorna automaticamente qualsiasi parte dell’interfaccia che dipende da questi valori. Questo è il cuore della reattività di Vue.

1.2 L’Istanza Vue e l’Options API

In Vue 3, iniziamo creando un’applicazione con createApp:

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

L’Options API è l’approccio tradizionale in Vue, dove definiamo un componente utilizzando un oggetto con varie opzioni:

export default {
  data() {
    return {
      message: 'Ciao mondo',
      counter: 0
    }
  },
  methods: {
    increment() {
      this.counter++
    }
  },
  computed: {
    doubleCounter() {
      return this.counter * 2
    }
  },
  watch: {
    counter(newValue, oldValue) {
      console.log(`Il contatore è cambiato da ${oldValue} a ${newValue}`)
    }
  }
}

Vue offre anche diversi hook di lifecycle che ci permettono di eseguire codice in momenti specifici durante la vita di un componente:

  • created: chiamato dopo che l’istanza è stata creata
  • mounted: chiamato dopo che il componente è stato montato nel DOM
  • updated: chiamato dopo che il componente è stato aggiornato
  • unmounted: chiamato dopo che il componente è stato rimosso dal DOM

1.3 Template Syntax e Directives

Vue utilizza un sistema di template basato su HTML con alcune estensioni. L’interpolazione è il modo più semplice per visualizzare dati nei template:

<div>{{ message }}</div>

Ma Vue offre anche diverse direttive che aggiungono funzionalità speciali agli elementi HTML:

  • v-if per rendering condizionale
  • v-for per iterare su array o oggetti
  • v-bind (o :) per assegnare valori agli attributi
  • v-on (o @) per ascoltare eventi
  • v-model per two-way binding

Ecco un esempio che usa alcune di queste direttive:

<div>
  <p v-if="mostrareParagrafo">Questo paragrafo è visibile</p>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.text }}</li>
  </ul>
  <button @click="aggiungiItem">Aggiungi Item</button>
  <input v-model="nuovoItem" placeholder="Nuovo item">
</div>

Vue permette anche di aggiungere classi e stili in modo dinamico:

<div :class="{ active: isActive, 'text-danger': hasError }"></div>
<div :style="{ color: textColor, fontSize: fontSize + 'px' }"></div>

2. Componenti in Vue.js

2.1 Creazione e Utilizzo dei Componenti

I componenti sono il blocco di costruzione fondamentale delle applicazioni Vue. In Vue 3, possiamo definire un componente in diversi modi. Uno dei più moderni è utilizzare la Composition API con defineComponent e setup:

import { defineComponent, ref } from 'vue'

export default defineComponent({
  props: {
    title: String
  },
  setup(props, { emit }) {
    const count = ref(0)
    
    function increment() {
      count.value++
      emit('incremented', count.value)
    }
    
    return {
      count,
      increment
    }
  }
})

Le props sono utilizzate per passare dati da un componente padre a un figlio. Il componente figlio deve dichiarare quali props accetta:

export default {
  props: {
    title: String,
    likes: {
      type: Number,
      default: 0
    },
    isPublished: {
      type: Boolean,
      required: true
    }
  }
}

Per la comunicazione figlio-padre, Vue utilizza gli eventi custom. Un componente figlio può emettere un evento con $emit (o emit nella Composition API), e il componente padre può ascoltarlo:

<!-- Componente figlio -->
<button @click="$emit('increment')">Incrementa</button>

<!-- Componente padre -->
<MyCounter @increment="handleIncrement" />

Gli slots sono un altro meccanismo importante per la composizione dei componenti, permettendo di iniettare contenuto dal componente padre al figlio:

<!-- Componente MyCard.vue -->
<div class="card">
  <div class="card-header">
    <slot name="header">Header predefinito</slot>
  </div>
  <div class="card-body">
    <slot>Contenuto predefinito</slot>
  </div>
  <div class="card-footer">
    <slot name="footer"></slot>
  </div>
</div>

<!-- Utilizzo -->
<MyCard>
  <template #header>Titolo della card</template>
  Questo è il contenuto principale
  <template #footer>Piè di pagina</template>
</MyCard>

2.2 Gestione dello Stato nei Componenti

Ogni componente può gestire il proprio stato locale. Con la Composition API, utilizziamo ref e reactive per creare stato reattivo:

import { ref, reactive } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const user = reactive({
      name: 'Mario',
      age: 30
    })
    
    function increment() {
      count.value++
    }
    
    function updateName(newName) {
      user.name = newName
    }
    
    return {
      count,
      user,
      increment,
      updateName
    }
  }
}

È importante capire la differenza tra props e stato interno. Le props sono dati passati da un componente genitore e non dovrebbero essere modificate direttamente dal componente figlio. Lo stato interno invece è controllato completamente dal componente stesso.

Vue offre anche v-model per il two-way binding, rendendo semplice la sincronizzazione tra form e dati:

<input v-model="message">
<p>Il messaggio è: {{ message }}</p>

2.3 Componenti Dinamici e Asincroni

Vue permette di cambiare dinamicamente il componente visualizzato usando <component> con la direttiva :is:

<component :is="currentComponent"></component>

Per migliorare le performance, possiamo anche caricare i componenti in modo asincrono solo quando servono:

import { defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
)

3. Gestione dello Stato Avanzata

3.1 Pinia (State Management Moderno)

Pinia è la soluzione moderna per la gestione dello stato in Vue, concepita come successore di Vuex. È più leggera, più tipizzata (con TypeScript) e si integra perfettamente con la Composition API.

Ecco come definire uno store con Pinia:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'Counter'
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    async fetchSomeData() {
      // azioni asincrone
    }
  }
})

E poi possiamo usarlo nei nostri componenti:

import { useCounterStore } from '@/stores/counter'

export default {
  setup() {
    const counterStore = useCounterStore()
    
    function incrementAndPrint() {
      counterStore.increment()
      console.log(counterStore.doubleCount)
    }
    
    return { counterStore, incrementAndPrint }
  }
}

3.2 Vuex (Per Applicazioni Legacy)

Vuex è stato lo stato manager standard per Vue 2 e molte applicazioni legacy lo utilizzano ancora. È basato sui concetti di state, mutations, actions e getters:

import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  },
  getters: {
    doubleCount: state => state.count * 2
  }
})

La principale differenza con Pinia è che Vuex è più verboso e utilizza un flusso unidirezionale più rigido dove lo stato può essere modificato solo attraverso mutations.

4. Vue Router (Gestione delle Rotte)

4.1 Configurazione Base

Vue Router è la soluzione ufficiale per gestire il routing nelle applicazioni Vue. Per cominciare, dobbiamo installarlo e configurarlo:

import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// E poi nella nostra app.js
app.use(router)

Nel template, possiamo usare <router-link> per navigare e <router-view> per visualizzare il componente corrispondente alla rotta attuale:

<nav>
  <router-link to="/">Home</router-link>
  <router-link to="/about">About</router-link>
</nav>

<router-view></router-view>

Possiamo anche navigare programmaticamente:

// Navigare a un nuovo URL
router.push('/about')

// Navigare con parametri
router.push({ name: 'user', params: { userId: '123' } })

4.2 Rotte Dinamiche e Nested Routes

Vue Router supporta parametri dinamici nelle rotte:

const routes = [
  { path: '/user/:id', component: User }
]

Nel componente User, possiamo accedere al parametro id con this.$route.params.id nell’Options API o useRoute().params.id nella Composition API.

Le route guards sono funzioni che vengono eseguite prima, durante o dopo la navigazione. Sono utili per il controllo dell’accesso:

router.beforeEach((to, from) => {
  if (to.meta.requiresAuth && !isAuthenticated) {
    return '/login'
  }
})

Vue Router supporta anche le rotte annidate:

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      { path: 'profile', component: UserProfile },
      { path: 'posts', component: UserPosts }
    ]
  }
]

4.3 Lazy Loading delle Rotte

Per migliorare le performance, possiamo caricare i componenti solo quando necessario:

const routes = [
  { path: '/about', component: () => import('./views/About.vue') }
]

Questo è particolarmente utile per applicazioni grandi, dove il caricamento iniziale potrebbe essere lento.

5. Composition API (Vue 3)

5.1 Perché usare Composition API?

La Composition API è stata introdotta in Vue 3 e rappresenta un cambiamento fondamentale nel modo in cui scriviamo componenti. Mentre l’Options API organizza il codice per tipo (data, methods, computed, ecc.), la Composition API permette di organizzarlo per funzionalità logica.

Il vantaggio principale è il maggiore riutilizzo del codice. Con l’Options API, estrarre logica riutilizzabile poteva essere complicato e richiedeva pattern come mixins che spesso causavano conflitti di nomi e problemi di manutenzione. La Composition API risolve questo problema permettendo di estrarre funzionalità in “composables” che possono essere importati e utilizzati in qualsiasi componente.

Inoltre, offre una migliore organizzazione della logica. In componenti complessi, la logica correlata spesso finiva sparsa tra diverse opzioni dell’Options API. Con la Composition API, possiamo raggruppare tutto il codice relativo a una specifica funzionalità, rendendo il componente più facile da leggere e mantenere.

// Con Options API
export default {
  data() {
    return { count: 0, name: 'Mario' }
  },
  methods: {
    increment() { this.count++ }
  },
  computed: {
    doubleCount() { return this.count * 2 }
  }
}

// Con Composition API
import { ref, computed } from 'vue'

export default {
  setup() {
    // Logica per il contatore
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)
    function increment() { count.value++ }
    
    // Logica per l'utente
    const name = ref('Mario')
    
    return { count, doubleCount, increment, name }
  }
}

5.2 ref vs reactive

In Vue 3, abbiamo due API principali per creare stato reattivo: ref e reactive.

ref è usato per creare un riferimento reattivo a un singolo valore. Funziona con qualsiasi tipo di valore, primitivo o oggetto, e incapsula il valore in un oggetto con una proprietà .value.

const count = ref(0) // Per primitivi
const user = ref({ name: 'Mario' }) // Anche per oggetti
console.log(count.value) // 0
count.value++ // Aggiornamento

reactive invece è usato per oggetti e rende reattive tutte le proprietà dell’oggetto in profondità. A differenza di ref, non richiede .value per accedere o modificare le proprietà.

const state = reactive({
  count: 0,
  user: { name: 'Mario' }
})
console.log(state.count) // 0
state.count++ // Aggiornamento diretto

Quando usare uno o l’altro? In generale:

  • Usa ref per valori primitivi (numeri, stringhe, boolean)
  • Usa ref quando hai bisogno di passare una variabile reattiva a una funzione
  • Usa reactive per oggetti complessi quando vuoi accedere direttamente alle proprietà senza .value

Per convertire tra i due formati, Vue fornisce le utility toRef e toRefs:

// Da reactive a ref
const state = reactive({ count: 0 })
const countRef = toRef(state, 'count')
countRef.value++ // Aggiorna anche state.count

// Convertire tutte le proprietà di un oggetto reactive in ref
const { count, user } = toRefs(state)
count.value++ // Aggiorna anche state.count

5.3 Composizione di Logica (Composables)

I composables sono una delle funzionalità più potenti della Composition API. Si tratta di funzioni che incapsulano e riutilizzano la logica stateful, simili agli hook di React.

Un composable tipicamente:

  1. Usa le API di composizione (ref, reactive, computed, ecc.)
  2. Ha una funzione che inizia con “use” per convenzione
  3. Restituisce un oggetto con valori e funzioni esposte

Ecco alcuni esempi:

// useFetch.js - un composable per fare richieste HTTP
import { ref, watchEffect } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(true)

  async function fetchData() {
    loading.value = true
    try {
      const response = await fetch(url)
      data.value = await response.json()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }

  watchEffect(() => {
    fetchData()
  })

  return { data, error, loading, refetch: fetchData }
}

// useStorage.js - un composable per il localStorage
import { ref, watch } from 'vue'

export function useStorage(key, defaultValue = null) {
  const value = ref(JSON.parse(localStorage.getItem(key)) || defaultValue)
  
  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  })
  
  return value
}

E poi usarli nei componenti:

import { useFetch } from './composables/useFetch'
import { useStorage } from './composables/useStorage'

export default {
  setup() {
    // Utilizzo di composables multipli nello stesso componente
    const { data: users, loading } = useFetch('https://api.example.com/users')
    const darkMode = useStorage('darkMode', false)
    
    return { users, loading, darkMode }
  }
}

6. Ottimizzazione e Performance

6.1 Virtual DOM e Reactivity Optimization

Vue usa un Virtual DOM per aggiornare il DOM in modo efficiente. Invece di aggiornare direttamente il DOM quando i dati cambiano, Vue crea una rappresentazione virtuale del DOM in memoria e poi confronta questa rappresentazione con il DOM reale, applicando solo le modifiche necessarie.

Questo processo, chiamato “diffing”, riduce drasticamente le operazioni costose sul DOM. Vue 3 ha anche migliorato questo meccanismo con:

  1. Flag statici che marcano le parti che non cambieranno mai
  2. Hoisting delle proprietà statiche
  3. Tree-shaking per rimuovere codice non utilizzato

Un aspetto importante è l’uso ottimale della prop key nelle direttive v-for. Fornire una chiave unica e stabile per ogni elemento aiuta Vue a identificare quali elementi sono cambiati, aggiunti o rimossi:

<!-- Non ottimale: usa l'indice come key -->
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>

<!-- Ottimale: usa un ID unico come key -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>

6.2 Lazy Loading e Code Splitting

Per applicazioni di grandi dimensioni, caricare tutto il codice in una volta può causare tempi di caricamento lunghi. Vue supporta il lazy loading dei componenti e delle rotte per dividere l’applicazione in pezzi più piccoli (code splitting) che vengono caricati solo quando necessario.

Per i componenti:

// Componente caricato solo quando utilizzato
const LazyComponent = defineAsyncComponent(() => 
  import('./components/HeavyComponent.vue')
)

Per le rotte in Vue Router:

const routes = [
  {
    path: '/dashboard',
    component: () => import('./views/Dashboard.vue')
  }
]

Questo approccio riduce significativamente il tempo di caricamento iniziale dell’applicazione.

6.3 Memoization con computed e watch

Vue offre potenti strumenti per ottimizzare calcoli costosi:

Le proprietà computed sono calcolate in base alle dipendenze reattive e sono memorizzate (memoized). Vengono ricalcolate solo quando una delle loro dipendenze cambia:

const list = ref([1, 2, 3, 4])

// Calcolato una volta e memorizzato
const doubledList = computed(() => {
  console.log('Calcolo doubledList')
  return list.value.map(n => n * 2)
})

// Non ricalcola se list non cambia
console.log(doubledList.value) // [2, 4, 6, 8]
console.log(doubledList.value) // [2, 4, 6, 8] (nessun calcolo)

list.value.push(5) // Ora ricalcola
console.log(doubledList.value) // [2, 4, 6, 8, 10] (ricalcolato)

watch permette di osservare e reagire ai cambiamenti di stato:

// Watch semplice
watch(searchQuery, (newValue) => {
  fetchResults(newValue)
})

// Opzioni avanzate
watch(searchQuery, (newValue) => {
  fetchResults(newValue)
}, {
  deep: true, // Osserva cambiamenti in profondità negli oggetti
  immediate: true, // Esegue subito alla creazione
  flush: 'post' // Esegue dopo l'aggiornamento del DOM
})

7. Testing in Vue.js

7.1 Unit Testing con Vitest o Jest

Il testing è una parte fondamentale dello sviluppo con Vue. La libreria ufficiale @vue/test-utils fornisce utility per testare i componenti Vue.

Ecco un esempio di test con Vitest (il sostituto moderno di Jest nell’ecosistema Vue):

import { mount } from '@vue/test-utils'
import { test, expect, vi } from 'vitest'
import Counter from './Counter.vue'

test('incrementa il counter quando cliccato', async () => {
  const wrapper = mount(Counter, {
    props: {
      initialCount: 0
    }
  })
  
  expect(wrapper.text()).toContain('0')
  
  await wrapper.find('button').trigger('click')
  
  expect(wrapper.text()).toContain('1')
})

test('emette eventi quando richiesto', async () => {
  const wrapper = mount(Counter)
  
  await wrapper.find('.reset-btn').trigger('click')
  
  expect(wrapper.emitted()).toHaveProperty('reset')
  expect(wrapper.emitted().reset[0]).toEqual([0])
})

Per mockare le props, gli eventi o le dipendenze esterne:

// Mockare props
const wrapper = mount(UserProfile, {
  props: {
    user: { id: 1, name: 'Mario' }
  }
})

// Mockare eventi globali
const $router = {
  push: vi.fn()
}
const wrapper = mount(NavBar, {
  global: {
    mocks: {
      $router
    }
  }
})

7.2 E2E Testing con Cypress o Playwright

I test end-to-end simulano il comportamento dell’utente e testano l’intera applicazione. Due strumenti popolari sono Cypress e Playwright.

Esempio con Cypress:

describe('App E2E', () => {
  it('esegue il login e naviga alla dashboard', () => {
    cy.visit('/')
    cy.get('input[name=email]').type('user@example.com')
    cy.get('input[name=password]').type('password123')
    cy.get('button[type=submit]').click()
    
    // Verifica che la navigazione funzioni
    cy.url().should('include', '/dashboard')
    cy.contains('Benvenuto, Utente').should('be.visible')
  })
})

Esempio con Playwright:

test('aggiunge un prodotto al carrello', async ({ page }) => {
  await page.goto('https://myshop.com')
  await page.click('text=Prodotti')
  await page.click('text=Smartphone XYZ')
  await page.click('button:has-text("Aggiungi al Carrello")')
  
  const cartCount = await page.locator('.cart-count').textContent()
  expect(cartCount).toBe('1')
})

8. Vue.js in Produzione

8.1 Build con Vite

Vite è diventato lo strumento di build predefinito per i nuovi progetti Vue 3, sostituendo webpack-based Vue CLI. È significativamente più veloce grazie all’uso dei moduli ES nativi durante lo sviluppo.

La configurazione di base di un progetto Vite per Vue è semplice:

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  // Configurazioni aggiuntive
  resolve: {
    alias: {
      '@': '/src' // Shorthand per i percorsi
    }
  },
  // Ottimizzazioni per la produzione
  build: {
    minify: 'terser',
    cssCodeSplit: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router', 'pinia']
        }
      }
    }
  }
})

Per avviare un progetto Vite:

npm create vite@latest my-vue-app -- --template vue
cd my-vue-app
npm install
npm run dev

8.2 SSR (Server-Side Rendering) con Nuxt.js

Nuxt.js è un framework basato su Vue che semplifica la creazione di applicazioni con server-side rendering. Offre un’esperienza di sviluppo migliorata con funzionalità pronte all’uso come routing basato su file, auto-import e SSR.

I vantaggi dell’SSR includono:

  1. Migliore SEO poiché i motori di ricerca vedono il contenuto completamente renderizzato
  2. Miglior performance percepita, con un tempo di caricamento iniziale più veloce
  3. Migliore esperienza utente, specialmente su dispositivi con connessioni lente

Un progetto base Nuxt 3:

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      title: 'La mia app Nuxt',
      meta: [
        { name: 'description', content: 'Descrizione della mia app' }
      ]
    }
  },
  modules: [
    '@pinia/nuxt',
    '@nuxtjs/tailwindcss'
  ],
  runtimeConfig: {
    apiSecret: '', // accessibile solo lato server
    public: {
      apiBase: '' // accessibile anche lato client
    }
  }
})

8.3 Static Site Generation (SSG)

Vue offre anche strumenti per la generazione di siti statici:

VuePress è progettato principalmente per la documentazione tecnica. Genera pagine HTML statiche pre-renderizzate che vengono caricate come SPA una volta nel browser.

// .vuepress/config.js
module.exports = {
  title: 'La mia documentazione',
  description: 'Documenti del mio progetto',
  themeConfig: {
    navbar: [
      { text: 'Home', link: '/' },
      { text: 'Guide', link: '/guide/' }
    ],
    sidebar: {
      '/guide/': [
        'getting-started',
        'configuration'
      ]
    }
  }
}

VitePress è il successore di VuePress basato su Vite, più veloce e con funzionalità moderne:

// .vitepress/config.js
export default {
  title: 'La mia documentazione',
  description: 'Alimentata da VitePress',
  themeConfig: {
    nav: [
      { text: 'Home', link: '/' },
      { text: 'Guide', link: '/guide/' }
    ],
    sidebar: [
      {
        text: 'Guide',
        items: [
          { text: 'Introduzione', link: '/guide/introduction' }
        ]
      }
    ]
  }
}

9. Best Practices e Pattern Comuni

Organizzazione del Progetto

Una struttura di progetto ben organizzata è essenziale per la manutenibilità:

src/
├── assets/           # Risorse statiche (immagini, font, ecc.)
├── components/       # Componenti riutilizzabili
│   ├── ui/           # Componenti UI generici
│   └── features/     # Componenti specifici per feature
├── composables/      # Composables (logica riutilizzabile)
├── layouts/          # Layout dell'applicazione
├── router/           # Configurazione del router
├── stores/           # Pinia stores
├── utils/            # Utility e funzioni helper
└── views/            # Componenti pagina

Naming Conventions

Adottare convenzioni di nomenclatura coerenti migliora la leggibilità:

  • Componenti: PascalCase sia per i nomi dei file che per la registrazione (es. UserProfile.vue)
  • Composables: camelCase con prefisso “use” (es. useUserData.js)
  • Direttive personali: kebab-case con prefisso “v-” (es. v-click-outside)
  • Props: camelCase nella definizione, kebab-case nel template (es. userName / user-name)

Gestione degli Errori

Una robusta gestione degli errori è cruciale per applicazioni di produzione:

// Composable per la gestione degli errori
export function useErrorHandling() {
  const error = ref(null)
  
  function handleError(e, customMessage = '') {
    console.error(e)
    error.value = {
      message: customMessage || e.message,
      original: e
    }
    
    // Potenziale integrazione con sistema di logging
    // logErrorToService(e)
  }
  
  function clearError() {
    error.value = null
  }
  
  return { error, handleError, clearError }
}

// Uso nel componente
const { error, handleError } = useErrorHandling()

async function fetchData() {
  try {
    // Operazione che potrebbe fallire
    const data = await api.get('/users')
    return data
  } catch (e) {
    handleError(e, 'Impossibile caricare gli utenti')
    return []
  }
}

10. Ecostistema Vue.js

Vue ha un ricco ecosistema di librerie che estendono le sue capacità:

UI Frameworks

  • Quasar: Framework completo con supporto per web, mobile e desktop
  • Vuetify: Framework Material Design con oltre 80 componenti
  • PrimeVue: Collezione di componenti UI ricchi
// Esempio con Vuetify
createApp(App)
  .use(vuetify)
  .mount('#app')

// In un componente
<template>
  <v-card>
    <v-card-title>Titolo Card</v-card-title>
    <v-card-text>Contenuto della card</v-card-text>
    <v-card-actions>
      <v-btn color="primary">OK</v-btn>
    </v-card-actions>
  </v-card>
</template>

Form Handling

  • VeeValidate: Validazione form completa e flessibile
  • FormKit: Sistema all-in-one per forms con validazione e UI
// VeeValidate esempio
import { Form, Field, ErrorMessage } from 'vee-validate'
import * as yup from 'yup'

const schema = yup.object({
  email: yup.string().required().email(),
  password: yup.string().required().min(8)
})

// Nel template
<Form :validation-schema="schema">
  <Field name="email" type="email" />
  <ErrorMessage name="email" />
  
  <Field name="password" type="password" />
  <ErrorMessage name="password" />
  
  <button type="submit">Login</button>
</Form>

Animazioni

  • vue-use/motion: API semplice per animazioni
  • GSAP: Potente libreria di animazione con integrazione Vue
// vue-use/motion esempio
import { useMotion } from '@vueuse/motion'

export default {
  setup() {
    const blockRef = ref(null)
    
    const motionInstance = useMotion(blockRef, {
      initial: { opacity: 0, y: 100 },
      enter: { 
        opacity: 1, 
        y: 0,
        transition: { duration: 800 }
      },
      hovered: { scale: 1.1 }
    })
    
    return { blockRef }
  }
}

11. Futuro di Vue.js

Evoluzione del Framework

Vue continua a evolversi con un focus su performance, developer experience e integrazione con l’ecosistema moderno. Il team di sviluppo è impegnato a mantenere la stabilità mentre introduce nuove funzionalità.

Le prossime versioni mirano a:

  • Migliorare ulteriormente le performance del compiler e del runtime
  • Espandere le API di basso livello per sviluppatori di librerie
  • Migliorare l’integrazione con i moderni strumenti di sviluppo

Vue 3.4 e Oltre

Vue 3.4 ha introdotto miglioramenti al compiler con ottimizzazioni significative delle performance e supporto migliorato per TypeScript. Le future versioni probabilmente includeranno:

  • Miglioramenti al sistema di reattività
  • Migliore integrazione con Vite e strumenti moderni
  • Espansione delle capacità della Composition API
  • Supporto migliorato per Suspense e funzionalità asincrone

Oltre alle funzionalità tecniche, la comunità Vue sta crescendo con sempre più aziende che adottano il framework per progetti enterprise. Questo porterà probabilmente a un ecosistema ancora più ricco di strumenti, librerie e best practices.

Conclusione

Vue.js è un framework maturo ma in continua evoluzione che riesce a bilanciare potenza e semplicità. La sua architettura progressiva lo rende adatto sia per piccoli progetti che per applicazioni enterprise complesse.

Con l’introduzione di Vue 3 e della Composition API, insieme a strumenti moderni come Vite e Pinia, sviluppare con Vue è diventato ancora più efficiente e piacevole. Sia che tu stia iniziando ora, sia che tu stia migrando da Vue 2, il framework offre un percorso chiaro e ben documentato.

Se non hai ancora provato Vue.js, è il momento perfetto per farlo!