Componenti
Single File Components (SFC)
I Single File Components sono il modo standard per scrivere componenti Vue. Un file .vue incapsula template, logica e stile in un unico file coeso.
<!-- MioComponente.vue -->
<script setup lang="ts">
// Logica del componente
import { ref } from 'vue'
const messaggio = ref('Ciao dal mio componente!')
</script>
<template>
<!-- Template HTML del componente -->
<div class="contenitore">
<h2>{{ messaggio }}</h2>
</div>
</template>
<style scoped>
/* Stili CSS incapsulati nel componente */
.contenitore {
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 8px;
}
</style>
Anatomia di un SFC
Un file .vue e composto da tre blocchi principali.
| Blocco | Obbligatorio | Descrizione |
|---|---|---|
<script setup> |
No (consigliato) | Logica del componente con Composition API |
<template> |
Si | Struttura HTML del componente |
<style> |
No | Stili CSS del componente |
Script setup
Il blocco <script setup> e la sintassi raccomandata per la Composition API. Tutto cio che viene dichiarato al livello superiore e automaticamente disponibile nel template.
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import CartaProdotto from './CartaProdotto.vue'
// Variabili reattive - automaticamente disponibili nel template
const titolo = ref('I nostri prodotti')
const prodotti = ref([
{ id: 1, nome: 'Laptop', prezzo: 999 },
{ id: 2, nome: 'Monitor', prezzo: 499 },
{ id: 3, nome: 'Tastiera', prezzo: 79 }
])
// Computed - disponibile nel template
const totaleProdotti = computed(() => prodotti.value.length)
// Funzioni - disponibili nel template
function rimuoviProdotto(id: number) {
prodotti.value = prodotti.value.filter(p => p.id !== id)
}
// Componenti importati - automaticamente registrati
// CartaProdotto e usabile direttamente nel template
onMounted(() => {
console.log('Componente montato')
})
</script>
<template>
<div>
<h1>{{ titolo }} ({{ totaleProdotti }})</h1>
<CartaProdotto
v-for="prodotto in prodotti"
:key="prodotto.id"
:prodotto="prodotto"
@rimuovi="rimuoviProdotto(prodotto.id)"
/>
</div>
</template>
Vantaggi di script setup
- Meno boilerplate: Non serve
export default,return, ocomponents - Performance migliore: Il compilatore ottimizza il codice
- TypeScript: Inferenza dei tipi migliore
- Componenti auto-registrati: Basta importarli
Script setup vs setup function
<!-- script setup (consigliato) -->
<script setup lang="ts">
import { ref } from 'vue'
const contatore = ref(0)
</script>
<!-- setup function (alternativa) -->
<script lang="ts">
import { ref, defineComponent } from 'vue'
export default defineComponent({
setup() {
const contatore = ref(0)
return { contatore }
}
})
</script>
Template
Il template definisce la struttura HTML del componente. Vue compila il template in funzioni di rendering ottimizzate.
Template con singolo elemento root
In Vue 3, i template possono avere piu elementi root (fragment).
<template>
<!-- Fragment: piu elementi root (Vue 3) -->
<header>Intestazione</header>
<main>Contenuto</main>
<footer>Piede pagina</footer>
</template>
Riferimenti al template (template refs)
Per accedere agli elementi DOM direttamente, usa ref nel template.
<script setup lang="ts">
import { ref, onMounted } from 'vue'
// Il nome deve corrispondere al ref nel template
const inputRef = ref<HTMLInputElement | null>(null)
const contenitoreRef = ref<HTMLDivElement | null>(null)
onMounted(() => {
// Focus automatico sull'input
inputRef.value?.focus()
// Accesso alle proprieta DOM
if (contenitoreRef.value) {
console.log('Altezza:', contenitoreRef.value.offsetHeight)
}
})
</script>
<template>
<div ref="contenitoreRef">
<input ref="inputRef" placeholder="Auto-focus" />
</div>
</template>
Ref su componenti figli
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import FormContatto from './FormContatto.vue'
const formRef = ref<InstanceType<typeof FormContatto> | null>(null)
onMounted(() => {
// Accesso ai metodi esposti dal componente figlio
formRef.value?.resetta()
})
</script>
<template>
<FormContatto ref="formRef" />
<button @click="formRef?.resetta()">Reset form</button>
</template>
Per esporre metodi dal componente figlio si usa defineExpose.
<!-- FormContatto.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const nome = ref('')
const email = ref('')
function resetta() {
nome.value = ''
email.value = ''
}
function validaForm() {
return nome.value.length > 0 && email.value.includes('@')
}
// Esponi solo cio che serve al componente genitore
defineExpose({
resetta,
validaForm
})
</script>
Style scoped
L’attributo scoped limita gli stili CSS al componente corrente. Vue aggiunge un attributo data-v-xxxxx unico a ogni elemento e lo usa come selettore CSS.
<style scoped>
/* Questi stili si applicano SOLO a questo componente */
.titolo {
color: #333;
font-size: 1.5rem;
}
button {
background: #42b883;
color: white;
}
</style>
Deep selector
Per applicare stili ai componenti figli all’interno di un componente scoped, usa :deep().
<style scoped>
/* Stile solo nel componente corrente */
.contenitore {
padding: 1rem;
}
/* Stile applicato ai figli (deep) */
.contenitore :deep(.figlio-classe) {
color: red;
}
/* Deep su un componente esterno */
:deep(.v-input__control) {
border-radius: 8px;
}
</style>
Slotted selector
Per applicare stili al contenuto passato via slot, usa :slotted().
<style scoped>
:slotted(p) {
color: #666;
line-height: 1.6;
}
:slotted(.evidenziato) {
background: yellow;
}
</style>
Global selector dentro scoped
Per dichiarare regole globali all’interno di un blocco scoped, usa :global().
<style scoped>
/* Scoped al componente */
.locale {
color: blue;
}
/* Globale anche se in blocco scoped */
:global(.classe-globale) {
color: red;
}
</style>
CSS Modules
Vue supporta anche CSS Modules come alternativa a scoped.
<script setup>
// $style e automaticamente disponibile con CSS Modules
</script>
<template>
<div :class="$style.contenitore">
<p :class="$style.testo">Testo con CSS Modules</p>
</div>
</template>
<style module>
.contenitore {
padding: 1rem;
}
.testo {
color: #333;
}
</style>
Blocchi style multipli
Un componente puo avere piu blocchi <style>.
<!-- Stili globali -->
<style>
body {
font-family: 'Inter', sans-serif;
}
</style>
<!-- Stili scoped al componente -->
<style scoped>
.componente {
padding: 1rem;
}
</style>
Organizzazione dei componenti
Struttura consigliata per progetti medi/grandi
src/
├── components/
│ ├── ui/ # Componenti UI generici
│ │ ├── BaseButton.vue
│ │ ├── BaseInput.vue
│ │ ├── BaseCard.vue
│ │ └── BaseModal.vue
│ ├── layout/ # Componenti layout
│ │ ├── AppHeader.vue
│ │ ├── AppSidebar.vue
│ │ └── AppFooter.vue
│ └── shared/ # Componenti condivisi
│ ├── IconaCaricamento.vue
│ └── MessaggioErrore.vue
├── features/ # Organizzazione per feature
│ ├── auth/
│ │ ├── components/
│ │ │ ├── FormLogin.vue
│ │ │ └── FormRegistrazione.vue
│ │ ├── composables/
│ │ │ └── useAuth.ts
│ │ └── stores/
│ │ └── authStore.ts
│ └── prodotti/
│ ├── components/
│ │ ├── ListaProdotti.vue
│ │ └── CartaProdotto.vue
│ ├── composables/
│ │ └── useProdotti.ts
│ └── stores/
│ └── prodottiStore.ts
└── views/
├── HomeView.vue
└── ProdottiView.vue
Convenzioni di naming
<!-- PascalCase per i componenti (consigliato) -->
<CartaProdotto />
<BaseButton />
<FormContatto />
<!-- kebab-case alternativo -->
<carta-prodotto />
<base-button />
Le convenzioni importanti da seguire sono le seguenti.
- Componenti multi-parola: Usa almeno due parole (
CartaProdotto, nonCarta) - Prefisso Base: Per componenti UI generici (
BaseButton,BaseInput) - Prefisso App: Per componenti singleton dell’app (
AppHeader,AppSidebar) - Prefisso The: Alternativa per singleton (
TheNavbar,TheFooter)
Componenti dinamici
<script setup lang="ts">
import { ref, shallowRef } from 'vue'
import TabHome from './TabHome.vue'
import TabProfilo from './TabProfilo.vue'
import TabImpostazioni from './TabImpostazioni.vue'
const tabs = [
{ nome: 'Home', componente: TabHome },
{ nome: 'Profilo', componente: TabProfilo },
{ nome: 'Impostazioni', componente: TabImpostazioni }
]
// Usa shallowRef per componenti (evita deep reactivity)
const tabCorrente = shallowRef(TabHome)
</script>
<template>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.nome"
:class="{ attivo: tab.componente === tabCorrente }"
@click="tabCorrente = tab.componente"
>
{{ tab.nome }}
</button>
</div>
<!-- Componente dinamico -->
<component :is="tabCorrente" />
</template>
Best practice
- Un componente, una responsabilita: Ogni componente deve fare una cosa sola e farla bene
- Usa
script setup: E piu conciso, performante e TypeScript-friendly - Nomina i componenti con PascalCase: Rende chiaro che sono componenti Vue nel template
- Usa
scopedper gli stili: Previene conflitti CSS involontari - Organizza per feature: Nelle app grandi, raggruppa per funzionalita invece che per tipo
- Mantieni i componenti piccoli: Se un componente supera le 200 righe, probabilmente va diviso
- Esponi solo il necessario: Usa
defineExposecon parsimonia