Sintassi dei Template
Interpolazione di testo
Vue utilizza la sintassi a doppia parentesi graffa (mustache) per il data binding nel template. L’espressione viene valutata e il risultato viene inserito come testo nel DOM.
<script setup>
import { ref } from 'vue'
const messaggio = ref('Ciao mondo!')
const contatore = ref(42)
const utente = ref({ nome: 'Mario', cognome: 'Rossi' })
</script>
<template>
<!-- Testo semplice -->
<p>{{ messaggio }}</p>
<!-- Espressioni JavaScript -->
<p>{{ contatore + 1 }}</p>
<p>{{ messaggio.split('').reverse().join('') }}</p>
<!-- Operatore ternario -->
<p>{{ contatore > 40 ? 'Grande' : 'Piccolo' }}</p>
<!-- Accesso a proprieta di oggetti -->
<p>{{ utente.nome }} {{ utente.cognome }}</p>
<!-- Chiamata a metodi -->
<p>{{ messaggio.toUpperCase() }}</p>
</template>
Limitazioni delle espressioni
Le interpolazioni supportano solo espressioni singole, non dichiarazioni o flussi di controllo.
<template>
<!-- Valido: espressione -->
<p>{{ ok ? 'Si' : 'No' }}</p>
<!-- NON valido: dichiarazione -->
<!-- <p>{{ let x = 1 }}</p> -->
<!-- NON valido: flusso di controllo -->
<!-- <p>{{ if (ok) { return 'Si' } }}</p> -->
</template>
v-bind: binding degli attributi
La direttiva v-bind lega dinamicamente un attributo HTML a un’espressione JavaScript. La sintassi abbreviata usa :.
<script setup>
import { ref } from 'vue'
const urlImmagine = ref('/img/logo.png')
const altTesto = ref('Logo del sito')
const idElemento = ref('contenitore-principale')
const isDisabilitato = ref(true)
const classeAttiva = ref('attivo')
</script>
<template>
<!-- Sintassi completa -->
<img v-bind:src="urlImmagine" v-bind:alt="altTesto" />
<!-- Sintassi abbreviata (consigliata) -->
<img :src="urlImmagine" :alt="altTesto" />
<!-- Binding dinamico dell'id -->
<div :id="idElemento">Contenuto</div>
<!-- Attributi booleani -->
<button :disabled="isDisabilitato">Invia</button>
<!-- Binding di classe -->
<div :class="classeAttiva">Elemento</div>
</template>
Binding di classi
Vue offre una sintassi avanzata per la gestione delle classi CSS.
<script setup>
import { ref } from 'vue'
const isAttivo = ref(true)
const hasErrore = ref(false)
const tipo = ref('primario')
</script>
<template>
<!-- Oggetto: chiave = classe, valore = condizione -->
<div :class="{ attivo: isAttivo, errore: hasErrore }">
Classi condizionali
</div>
<!-- Array di classi -->
<div :class="[tipo, { attivo: isAttivo }]">
Mix array e oggetto
</div>
<!-- Con classe statica -->
<div class="base" :class="{ attivo: isAttivo }">
Classe statica + dinamica
</div>
</template>
Binding di stili inline
<script setup>
import { ref, computed } from 'vue'
const colore = ref('#42b883')
const dimensione = ref(18)
const stileComputed = computed(() => ({
color: colore.value,
fontSize: `${dimensione.value}px`,
fontWeight: 'bold'
}))
</script>
<template>
<!-- Oggetto stile -->
<p :style="{ color: colore, fontSize: dimensione + 'px' }">
Testo colorato
</p>
<!-- Stile computed -->
<p :style="stileComputed">Testo con stile computed</p>
<!-- Array di stili -->
<p :style="[stileComputed, { textDecoration: 'underline' }]">
Stili multipli
</p>
</template>
v-on: gestione eventi
La direttiva v-on ascolta eventi DOM e esegue codice quando vengono emessi. La sintassi abbreviata usa @.
<script setup>
import { ref } from 'vue'
const contatore = ref(0)
const nome = ref('')
function saluta(evento: Event) {
alert(`Ciao! Evento: ${evento.type}`)
}
function incrementaDi(n: number) {
contatore.value += n
}
</script>
<template>
<!-- Espressione inline -->
<button @click="contatore++">+1</button>
<!-- Metodo handler -->
<button @click="saluta">Saluta</button>
<!-- Metodo con argomenti -->
<button @click="incrementaDi(5)">+5</button>
<!-- Accesso all'evento nativo con $event -->
<input @input="nome = ($event.target as HTMLInputElement).value" />
<!-- Eventi multipli -->
<button
@mouseenter="contatore++"
@mouseleave="contatore--"
>
Hover: {{ contatore }}
</button>
</template>
Modificatori di eventi
I modificatori sono suffissi speciali che modificano il comportamento degli event handler.
<template>
<!-- Previeni il comportamento predefinito -->
<form @submit.prevent="invia">
<button type="submit">Invia</button>
</form>
<!-- Stoppa la propagazione -->
<div @click="esterno">
<button @click.stop="interno">Non propaga</button>
</div>
<!-- Esegui solo una volta -->
<button @click.once="inizializza">Solo una volta</button>
<!-- Solo se il target e l'elemento stesso -->
<div @click.self="soloSeSelf">
<button>Click su button non attiva il div</button>
</div>
<!-- Cattura nella fase di capturing -->
<div @click.capture="cattura">Capturing</div>
<!-- Modificatori della tastiera -->
<input @keyup.enter="invia" />
<input @keyup.esc="annulla" />
<input @keyup.tab="prossimoCampo" />
<!-- Combinazioni di tasti -->
<input @keyup.ctrl.enter="inviaConCtrl" />
<input @keyup.alt.s="salva" />
<!-- Modificatori del mouse -->
<button @click.left="clickSinistro">Solo sinistro</button>
<button @click.right.prevent="menuContestuale">Destro</button>
<button @click.middle="clickCentrale">Centrale</button>
</template>
v-model: binding bidirezionale
v-model crea un binding bidirezionale tra un input e una variabile reattiva.
<script setup>
import { ref } from 'vue'
const testo = ref('')
const numero = ref(0)
const accettato = ref(false)
const coloreSelezionato = ref('rosso')
const linguaggi = ref<string[]>([])
const commento = ref('')
</script>
<template>
<!-- Input testo -->
<input v-model="testo" placeholder="Scrivi qualcosa" />
<!-- Textarea -->
<textarea v-model="commento" rows="3"></textarea>
<!-- Checkbox singolo (booleano) -->
<label>
<input type="checkbox" v-model="accettato" />
Accetto i termini
</label>
<!-- Checkbox multipli (array) -->
<label>
<input type="checkbox" v-model="linguaggi" value="javascript" />
JavaScript
</label>
<label>
<input type="checkbox" v-model="linguaggi" value="typescript" />
TypeScript
</label>
<!-- Radio -->
<label>
<input type="radio" v-model="coloreSelezionato" value="rosso" />
Rosso
</label>
<label>
<input type="radio" v-model="coloreSelezionato" value="verde" />
Verde
</label>
<!-- Select -->
<select v-model="coloreSelezionato">
<option value="rosso">Rosso</option>
<option value="verde">Verde</option>
<option value="blu">Blu</option>
</select>
</template>
Modificatori di v-model
<template>
<!-- .lazy: aggiorna su "change" invece di "input" -->
<input v-model.lazy="testo" />
<!-- .number: converte automaticamente a numero -->
<input v-model.number="eta" type="number" />
<!-- .trim: rimuove spazi iniziali e finali -->
<input v-model.trim="nome" />
</template>
Rendering condizionale
v-if, v-else-if, v-else
Queste direttive aggiungono o rimuovono elementi dal DOM in base a una condizione.
<script setup>
import { ref } from 'vue'
const punteggio = ref(85)
const isAutenticato = ref(true)
const ruolo = ref('admin')
</script>
<template>
<!-- Condizione semplice -->
<p v-if="isAutenticato">Benvenuto!</p>
<p v-else>Effettua il login</p>
<!-- Catena condizionale -->
<div v-if="punteggio >= 90">Eccellente!</div>
<div v-else-if="punteggio >= 70">Buono</div>
<div v-else-if="punteggio >= 50">Sufficiente</div>
<div v-else>Insufficiente</div>
<!-- Condizionale su gruppo con template -->
<template v-if="ruolo === 'admin'">
<h2>Pannello Admin</h2>
<p>Hai accesso completo</p>
<button>Gestisci utenti</button>
</template>
</template>
v-show
v-show usa display: none per nascondere l’elemento senza rimuoverlo dal DOM. Preferibile quando si alterna frequentemente la visibilita.
<script setup>
import { ref } from 'vue'
const mostraDettagli = ref(false)
</script>
<template>
<button @click="mostraDettagli = !mostraDettagli">
{{ mostraDettagli ? 'Nascondi' : 'Mostra' }} dettagli
</button>
<!-- v-show: sempre nel DOM, toggle CSS display -->
<div v-show="mostraDettagli">
<p>Dettagli che si mostrano/nascondono frequentemente</p>
</div>
</template>
Quando usare v-if vs v-show
| Caratteristica | v-if | v-show |
|---|---|---|
| Rendering | Lazy (solo quando true) | Sempre renderizzato |
| Costo toggle | Alto (crea/distrugge DOM) | Basso (cambia CSS) |
| Costo iniziale | Basso se false | Alto (sempre renderizzato) |
| Usa quando | Condizione cambia raramente | Toggle frequente |
Rendering di liste con v-for
<script setup>
import { ref } from 'vue'
const frutta = ref(['Mela', 'Banana', 'Arancia', 'Kiwi'])
const utenti = ref([
{ id: 1, nome: 'Mario', attivo: true },
{ id: 2, nome: 'Luigi', attivo: false },
{ id: 3, nome: 'Peach', attivo: true }
])
const configurazione = ref({ tema: 'scuro', lingua: 'it', notifiche: true })
</script>
<template>
<!-- Array semplice con indice -->
<ul>
<li v-for="(frutto, indice) in frutta" :key="indice">
{{ indice + 1 }}. {{ frutto }}
</li>
</ul>
<!-- Array di oggetti (usa sempre :key con id unico) -->
<div v-for="utente in utenti" :key="utente.id">
<span :class="{ attivo: utente.attivo }">{{ utente.nome }}</span>
</div>
<!-- Iterazione su oggetto -->
<div v-for="(valore, chiave) in configurazione" :key="chiave">
{{ chiave }}: {{ valore }}
</div>
<!-- Range numerico -->
<span v-for="n in 5" :key="n">{{ n }} </span>
<!-- v-for con template per gruppi -->
<template v-for="utente in utenti" :key="utente.id">
<h3>{{ utente.nome }}</h3>
<p>Stato: {{ utente.attivo ? 'Attivo' : 'Inattivo' }}</p>
<hr />
</template>
</template>
v-for con v-if
Non usare v-if sullo stesso elemento di v-for. Usa un <template> wrapper o una computed property.
<script setup>
import { ref, computed } from 'vue'
const utenti = ref([
{ id: 1, nome: 'Mario', attivo: true },
{ id: 2, nome: 'Luigi', attivo: false },
{ id: 3, nome: 'Peach', attivo: true }
])
// Approccio corretto: computed che filtra
const utentiAttivi = computed(() =>
utenti.value.filter(u => u.attivo)
)
</script>
<template>
<!-- Corretto: usa computed filtrata -->
<li v-for="utente in utentiAttivi" :key="utente.id">
{{ utente.nome }}
</li>
<!-- Alternativa: template wrapper -->
<template v-for="utente in utenti" :key="utente.id">
<li v-if="utente.attivo">{{ utente.nome }}</li>
</template>
</template>
Binding dinamico di attributi multipli
<script setup>
const attributiInput = {
id: 'campo-email',
type: 'email',
placeholder: 'Inserisci email',
required: true,
class: 'input-campo'
}
</script>
<template>
<!-- Spread di tutti gli attributi -->
<input v-bind="attributiInput" />
</template>
Best practice
- Usa sempre
:keyunico conv-for, preferibilmente un ID e non l’indice - Preferisci la sintassi abbreviata (
:perv-bind,@perv-on) - Non mischiare
v-forev-ifsullo stesso elemento - Usa
v-showper toggle frequenti ev-ifper condizioni che cambiano raramente - Mantieni le espressioni nel template semplici e usa computed per logica complessa
- Usa
<template>come wrapper invisibile per raggruppare elementi con direttive