Passa al contenuto

Nozioni base sui Componenti

I componenti ci permettono di dividere l'interfaccia utente in parti indipendenti e riutilizzabili, e di pensare a ciascuna parte in modo separato. È normale che un'app sia organizzata in un albero di componenti annidati:

Albero dei Componenti

Questo è molto simile al modo in cui si annidano gli elementi HTML nativi, ma Vue implementa il proprio modello di componenti che ci permette di incapsulare contenuti e logica personalizzati in ciascun componente. Vue funziona bene anche con i Web Components nativi. Se sei curioso di conoscere la relazione tra i Componenti Vue e i Web Components nativi, leggi di più qui.

Definizione di un Componente

Quando si utilizza uno strumento di build, di solito definiamo ogni componente Vue in un file dedicato utilizzando l'estensione .vue - noto come Componente in un Singolo File (SFC in breve):

vue
<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">Mi hai cliccato {{ count }} volte.</button>
</template>
vue
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">Mi hai cliccato {{ count }} volte.</button>
</template>

Quando non si utilizza uno strumento di build, un componente Vue può essere definito come un semplice oggetto JavaScript contenente opzioni specifiche per Vue:

js
export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      Mi hai cliccato {{ count }} volte.
    </button>`
}
js
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      Mi hai cliccato {{ count }} volte.
    </button>`
  // È anche possibile puntare a un template nel DOM:
  // template: '#my-template-element'
}

TIl template qui è inserito direttamente come stringa JavaScript, che Vue compilerà immediatamente. Puoi anche utilizzare un selettore ID che punta a un elemento (solitamente elementi <template> nativi) - Vue utilizzerà il suo contenuto come sorgente del template.

L'esempio sopra definisce un singolo componente e lo esporta come un export predefinito di un file .js, ma puoi utilizzare export con nomi dedicati per esportare più componenti dallo stesso file.

Utilizzo di un Componente

TIP

Utilizzeremo la sintassi SFC per il resto di questa guida - i concetti riguardanti i componenti sono gli stessi, indipendentemente dal fatto che si utilizzi uno strumento di build o meno. La sezione Esempi mostra l'utilizzo dei componenti in entrambi gli scenari.

Per utilizzare un componente figlio, dobbiamo importarlo nel componente genitore. Supponendo di aver collocato il nostro componente contatore all'interno di un file chiamato ButtonCounter.vue, il componente sarà esposto come export predefinito del file:

vue
<script>
import ButtonCounter from './ButtonCounter.vue'

export default {
  components: {
    ButtonCounter
  }
}
</script>

<template>
  <h1>Ecco un componente figlio!</h1>
  <ButtonCounter />
</template>

Per esporre il componente importato nel nostro template, dobbiamo registrarlo con l'opzione components. Il componente sarà quindi disponibile come un tag utilizzando la chiave con cui è registrato.

vue
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>Ecco un componente figlio!</h1>
  <ButtonCounter />
</template>

Con <script setup>, i componenti importati sono disponibili nel template automaticamente.

È anche possibile registrare globalmente un componente, rendendolo disponibile a tutti i componenti in una determinata app senza doverlo importare. I pro e i contro della registrazione globale rispetto a quella locale sono discussi nella sezione dedicata alla Registrazione dei Componenti.

I Componenti possono essere riutilizzati quante volte si vuole:

template
<h1>Ecco molti componenti figli!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

Nota che quando si fa clic sui pulsanti, ognuno mantiene il proprio count separato. Questo succede perché ogni volta che si utilizza un componente, ne viene creata una nuova istanza.

Negli SFC, si consiglia di utilizzare i nomi dei tag in PascalCase per i componenti figli per differenziarli dagli elementi HTML nativi. Sebbene i nomi dei tag HTML nativi siano insensibili alle maiuscole/minuscole (case-insensitive), Vue SFC è un formato compilato, quindi siamo in grado di utilizzare nomi di tag sensibili alle maiuscole/minuscole (case-sensitive). Siamo anche in grado di utilizzare /> per chiudere un tag.

Se stai usando i tuoi template direttamente in un DOM (ad es. come contenuto di un elemento <template> nativo), il template sarà soggetto al comportamento di analisi HTML nativo del browser. In tali casi, sarà necessario utilizzare la kebab-case e esplicitare i tag di chiusura per i componenti:

template
<!-- se questo template è scritto nel DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

Consulta i problemi dell'analisi del template DOM per maggiori dettagli.

Passaggio delle Props

Se stiamo costruendo un blog, avremo probabilmente bisogno di un componente che rappresenti un post del blog. Vogliamo che tutti i post del blog condividano lo stesso layout visuale, ma con contenuti diversi. Un componente del genere non sarà utile a meno che non vi si possa passare dei dati, come il titolo e il contenuto dello specifico post che vogliamo visualizzare. È qui che entrano in gioco le props.

Le props sono attributi personalizzati che puoi registrare su un componente. Per passare un titolo al nostro componente del post del blog, dobbiamo dichiararlo nell'elenco delle props che questo componente accetta, utilizzando l'opzione propsdefineProps macro:

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['title']
}
</script>

<template>
  <h4>{{ title }}</h4>
</template>

Quando un valore viene passato a un attributo prop, diventa una proprietà di quell'istanza del componente. Il valore di quella proprietà è accessibile all'interno del template e nel contesto this del componente, proprio come qualsiasi altra proprietà del componente.

vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>

definePropsè una macro che viene elaborata al momento della compilazione (compile-time) ed è disponibile solo all'interno di <script setup> senza bisogno di essere importata esplicitamente. Le props dichiarate sono automaticamente esposte nel template. defineProps restituisce anche un oggetto che contiene tutte le props passate al componente, in modo che possiamo accedervi in JavaScript se necessario:

js
const props = defineProps(['title'])
console.log(props.title)

Vedi anche: Tipizzazione delle Prop di un Componente

Se non stai utilizzando <script setup>, le props dovrebbero essere dichiarate utilizzando l'opzione props, e l'oggetto delle props verrà passato a setup() come primo argomento:

js
export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

Un componente può avere tutte le props che desideri e, di default, qualsiasi valore può essere passato a qualsiasi prop.

Una volta registrata una prop, puoi passare dati ad essa come attributo personalizzato, in questo modo:

template
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

In un'app tipica, tuttavia, è probabile che tu abbia un array di post nel tuo componente genitore:

js
export default {
  // ...
  data() {
    return {
      posts: [
        { id: 1, title: 'My journey with Vue' },
        { id: 2, title: 'Blogging with Vue' },
        { id: 3, title: 'Why Vue is so fun' }
      ]
    }
  }
}
js
const posts = ref([
  { id: 1, title: 'My journey with Vue' },
  { id: 2, title: 'Blogging with Vue' },
  { id: 3, title: 'Why Vue is so fun' }
])

E per ciascuna voce, vorresti poter renderizzare un componente utilizzando v-for:

template
<BlogPost
  v-for="post in posts"
  :key="post.id"
  :title="post.title"
 />

Nota come v-bind venga utilizzato per passare valori di props dinamici. Questo è particolarmente utile quando non si conosce in anticipo l'esatto contenuto che si andrà a renderizzare.

Questo è tutto ciò che c'è da sapere sulle props per ora, ma una volta che avrai finito di leggere questa pagina e ti sentirai a tuo agio con il suo contenuto, ti consigliamo di tornare in seguito a leggere la guida completa sulle Props.

Ascolto degli Eventi

Mentre sviluppiamo il nostro componente <BlogPost>, alcune funzionalità potrebbero richiedere di comunicare nuovamente con il componente genitore. Ad esempio, potremmo decidere di includere una funzionalità di accessibilità per ingrandire il testo dei post del blog, lasciando il resto della pagina alla sua dimensione predefinita.

Nel componente genitore possiamo aggiungere questa funzionalità tramite una proprietà dataref postFontSize:

js
data() {
  return {
    posts: [
      /* ... */
    ],
    postFontSize: 1
  }
}
js
const posts = ref([
  /* ... */
])

const postFontSize = ref(1)

La quale può essere utilizzata nel template per controllare la dimensione del carattere di tutti i post del blog:

template
<div :style="{ fontSize: postFontSize + 'em' }">
  <BlogPost
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
   />
</div>

Ora aggiungiamo un pulsante al template del componente <BlogPost>:

vue
<!-- BlogPost.vue, omesso <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>Enlarge text</button>
  </div>
</template>

Il pulsante non fa ancora nulla: vogliamo che fare clic sul pulsante comunichi al genitore che dovrebbe ingrandire il testo di tutti i post. Per risolvere questo problema, i componenti forniscono un sistema di eventi personalizzati. Il componente genitore può scegliere di ascoltare qualsiasi evento sull'istanza del componente figlio con v-on o @, proprio come faremmo con un evento DOM nativo:

template
<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

Poi il componente figlio può emettere un evento esso stesso usando il metodo integrato $emit, passando il nome dell'evento:

vue
<!-- BlogPost.vue, omesso <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

Grazie al listener @enlarge-text="postFontSize += 0.1", il componente genitore riceverà l'evento e aggiornerà il valore di postFontSize.

Facoltativamente possiamo dichiarare gli eventi emessi usando l'opzione emitsdefineEmits macro:

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['title'],
  emits: ['enlarge-text']
}
</script>
vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

Ciò documenta tutti gli eventi che un componente può emettere e, facoltativamente, ne effettua la convalida. Inoltre, consente a Vue di evitare l'applicazione implicita come listener nativi sull'elemento radice del componente figlio.

In modo simile a defineProps, defineEmits è utilizzabile soltanto all'interno di <script setup> e non necessita di essere importato. Restituisce una funzione emit che funziona in maniera equivalente al metodo $emit. Questa può essere usata per usare eventi nella sezione <script setup> di un componente, là dove $emit non è direttamente accessibile.

vue
<script setup>
const emit = defineEmits(['enlarge-text'])

emit('enlarge-text')
</script>

Vedi anche: Tipizzazione degli Emits di un Componente

Se non stai utilizzando <script setup>, puoi dichiarare gli eventi emessi mediante l'opzione emits. Puoi accedere alla funzione emit come una proprietà del contesto di configurazione (fornito a setup() come secondo argomento).

js
export default {
  emits: ['enlarge-text'],
  setup(props, ctx) {
    ctx.emit('enlarge-text')
  }
}

Questo è tutto ciò che c'è da sapere al momento sugli eventi personalizzati dei componenti, ma una volta che avrai finito di leggere questa pagina e ti sentirai a tuo agio con il suo contenuto, ti suggeriamo di tornare in seguito a leggere la guida completa sugli Eventi Personalizzati.

Distribuzione del Contenuto con gli Slot

Come avviene con gli elementi HTML, è spesso utile poter trasmettere contenuto a un componente nel seguente modo:

template
<AlertBox>
  Qualcosa è andato storto.
</AlertBox>

Ciò potrebbe produrre un risultato simile a:

Questo è un Errore a Scopo Dimostrativo

Qualcosa è andato storto.

Questo risultato può essere ottenuto usando l'elemento personalizzato <slot> di Vue:

vue
<template>
  <div class="alert-box">
    <strong>Questo è un Errore a Scopo Dimostrativo</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>

Come potrai vedere, utilizziamo il <slot> come contenitore per indicare dove posizionare il contenuto. E con questo, abbiamo concluso!

Questo è tutto ciò che c'è da sapere sugli slot per il momento, ma una volta che avrai terminato la lettura di questa pagina e ti sentirai a tuo agio con il suo contenuto, ti suggeriamo di tornare in seguito a leggere la guida completa sugli Slots.

Componenti Dinamici

A volte, può essere utile passare dinamicamente da un componente all'altro, come in un'interfaccia a schede (tab):

Quanto visto sopra è reso possibile dall'elemento <component> di Vue con l'attributo speciale is:

template
<!-- Il componente cambia quando cambia currentTab -->
<component :is="currentTab"></component>
template
<!-- Il componente cambia quando cambia currentTab -->
<component :is="tabs[currentTab]"></component>

Nell'esempio sopra, il valore passato a :is può contenere:

  • la stringa del nome di un componente registrato, OPPURE
  • l'oggetto del componente attualmente importato

Puoi anche utilizzare l'attributo is per creare elementi HTML regolari.

Quando si passa tra più componenti con <component :is="...">, il componente verrà smontato (unmounted) quando si passa ad un altro. Possiamo costringere i componenti inattivi a rimanere "vivi" con il componente <KeepAlive> integrato.

Limitazioni nel Parsing dei DOM Template

Se stai scrivendo i tuoi template Vue direttamente nel DOM, Vue dovrà recuperare la stringa del template dal DOM. Ciò porta ad alcune limitazioni a causa del comportamento di analisi di HTML nativo dei browser.

TIP

Va notato che le limitazioni discusse di seguito si applicano solo se stai scrivendo i tuoi template direttamente nel DOM. NON si applicano se stai utilizzando stringhe di template dalle seguenti fonti:

  • Componenti Single-File
  • Stringhe di template in linea (e.g. template: '...')
  • <script type="text/x-template">

Case Insensitivity

I tag HTML e i nomi degli attributi sono insensibili al maiuscolo/minuscolo (Case Insensitive), quindi i browser interpreteranno i caratteri maiuscoli come tutti minuscoli. Ciò significa che quando utilizzi i template in-DOM, i nomi dei componenti PascalCase e i nomi delle prop camelCased o i nomi degli eventi v-on devono tutti utilizzare i loro equivalenti in kebab-case (delimitati da trattini):

js
// camelCase in JavaScript
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
template
<!-- kebab-case in HTML -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

Tag di Chiusura Automatici

Abbiamo utilizzato tag di chiusura automatici per i componenti nei precedenti esempi di codice:

template
<MyComponent />

Questo perché il parser di template di Vue rispetta /> come indicazione per terminare qualsiasi tag, indipendentemente dal suo tipo.

Nei template in-DOM, tuttavia, dobbiamo sempre includere tag di chiusura espliciti:

template
<my-component></my-component>

Questo perché lo standard HTML consente di omettere i tag di chiusura solo per alcuni elementi specifici, i più comuni dei quali sono <input> e <img>. Per tutti gli altri elementi, se ometti il tag di chiusura, il parser HTML nativo penserà che non hai mai terminato il tag di apertura. Ad esempio, il seguente frammento:

template
<my-component /> <!-- intendiamo chiudere il tag qui... -->
<span>hello</span>

verrà analizzato come:

template
<my-component>
  <span>hello</span>
</my-component> <!-- e il browser lo chiuderà qui. -->

Restrizioni sulla Posizione degli Elementi

Alcuni elementi HTML, come <ul>, <ol>, <table> e <select> hanno delle restrizioni su quali elementi possono apparire al loro interno, e alcuni elementi come <li>, <tr> e <option> possono apparire solo all'interno di certi altri elementi.

Ciò porterà a dei problemi quando si utilizzano componenti con elementi che hanno tali restrizioni. Ad esempio:

template
<table>
  <blog-post-row></blog-post-row>
</table>

Il componente personalizzato <blog-post-row> verrà segnalato come contenuto non valido, causando errori nell'output renderizzato finale. Possiamo utilizzare lo speciale attributo is come soluzione alternativa:

template
<table>
  <tr is="vue:blog-post-row"></tr>
</table>

TIP

Quando utilizzato su elementi HTML nativi, il valore di is deve essere prefissato con vue: per essere interpretato come componente Vue. Ciò è necessario per evitare confusione con gli (elementi nativi personalizzati).

Questo è tutto ciò che devi sapere sulle limitazioni nel Parsing dei DOM template per ora - e in realtà, la fine degli Gli Elementi Essenziali di Vue. Congratulazioni! C'è ancora molto da imparare, ma prima, ti consigliamo di prenderti una pausa per giocare con Vue da solo - costruisci qualcosa di divertente o dai un'occhiata ad alcuni degli Esempi se non lo hai già fatto.

Una volta che ti sentirai a tuo agio con le conoscenze che hai appena assimilato, prosegui con la guida per approfondire ulteriormente i componenti.

Nozioni base sui Componenti has loaded