00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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, o components
  • 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, non Carta)
  • 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 scoped per 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 defineExpose con parsimonia