back to top

Vue.js : composizione di componenti con gli Slot

Nelle precedenti lezioni abbiamo visto come passare delle informazioni ad un componente tramite le cosiddette ‘props’ che rappresentano infatti un mezzo di comunicazioni attraverso il quale un componente genitore trasferisce dei dati agli immediati discendenti presenti nel suo template.

Nel template di un componente è anche possibile aggiungere dei segnaposto in cui iniettare qualsiasi tipo di contenuto quando il componente viene utilizzato nel template di un altro componente genitore.

Infatti, nel linguaggio HTML standard posizioniamo fra i tag di apertura e chiusura di certi elementi i loro diretti discendenti che, allo stesso modo, possono avere altri elementi figli.

<div>
  <h2>Giuseppe Verdi</h2>
  <p>Front-end Developer and Web Designer</p>
</div>

Vogliamo ottenere lo stesso risultato per i componenti Vue da noi definiti. Considerato però che per un componente personalizzato definiamo un template composto da elementi standard o associati ad altri componenti, è lecito chiedersi in quale esatto punto del template vengono incastonati gli elementi iniettati fra i suoi tag di apertura e chiusura.

In risposta a questa domanda, Vue fornisce l’elemento <slot></slot> grazie al quale potremo indicare in quale punto del template di un componente vogliamo ritagliare uno spazio riservato agli elementi che verranno successivamente iniettati dall’esterno.

Se quanto detto può risultare vago ed astratto, un esempio pratico illustrerà sicuramente meglio come trarre vantaggio delle funzionalità offerte da Vue tramite gli slot.

Consideriamo allora il frammento di codice riportato sotto relativo ad un componente <App> che abbiamo definito nell’omonimo file con estensione .vue e che, in base a quanto illustrato nella precedente lezione su Vue CLI, potremo visualizzare tramite la funzione di Instant Prototyping eseguendo nel terminale il comando vue serve (all’interno della cartella in cui è presente il file App.vue).

// App.vue
<template>
  <!-- Elementi inseriti fra i tag di apertura e chiusura -->
  <!-- del componente Card -->
  <Card>
    <img src="http://source.unsplash.com/26vBUtlufFo/96x96" alt="immagine profilo">
    <div>
      <h2>Giuseppe Verdi</h2>
      <p>Front-end Developer & Web Designer</p>
    </div>
  </Card>
</template>

<script>
import Card from './Card.vue';

export default {
  name: 'App',
  components: {
    Card
  }
}
</script>

All’interno del template di <App> importiamo un componente <Card> e tra i suoi tag di apertura e chiusura inseriamo alcuni elementi HTML predefiniti. Per indicare poi in quale punto del template di <Card> devono essere disposti tali elementi, useremo un elemento <slot><slot>.

Spostiamoci quindi nel file Card.vue e nel template del componente posizioniamo un elemento <slot><slot> che sarà quindi sostituito in fase di compilazione del template con gli elementi che abbiamo usato nel frammento di codice riportato sopra.

<template>
  <div class="card-container">
    <slot></slot>
  </div>
</template>

<style scoped>
.card-container {
  max-width: 480px;
  min-height: 64px;
  border-radius: 16px;
  padding: 1.5rem;
  box-shadow: 0px 4px 8px hsla(0, 0%, 0%, .14);
  background-color: #fff;
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  display: flex;
  align-items: center;
}

h2 {
  color: hsl(206, 100%, 21%);
  margin: 0;
}

p {
  color: hsl(206, 15%, 54%);
  margin: .2rem 0 0;
}

img {
  border-radius: 50%;
  margin-right: 1.5rem;
  width: 96px;
  height: 96px;
}
</style>

Otteniamo così il risultato riportato nell’immagine sottostante.

esempio in Vue.js che fa uso degli slot

Se apriamo il pannello ‘Elements’ degli strumenti per sviluppatori del browser, possiamo osservare che gli elementi passati fra i tag del componente <Card> sono stati inseriti al posto del segnaposto <slot> presente nel suo template.

struttura del dom in Vue.js ottenuta usando gli slot

Il componente <Card> può quindi essere visto come un contenitore all’interno del quale inseririamo successivamente gli elementi predefiniti o personalizzati che preferiamo.

Nell’esempio appena mostrato, abbiamo anche abbozzato delle regole CSS per evidenziare il fatto che queste vengono comunque applicate agli elementi disposti fra i tag di apertura e chiusura del componente <Card> anche se utilizziamo l’attributo scoped sul tag <style> presente nel file Card.vue

Contenuto predefinito per gli slot

Ma cosa succede se fra i tag di apertura e chiusura di <Card> non è presente alcun elemento? In tal caso al posto di <slot></slot> non sarà inserito nulla e l’elemento <slot></slot> non verrà comunque aggiunto al DOM.

Nell’esempio appena illustrato, visto che abbiamo stabilito un’altezza minima attraverso delle proprietà CSS per il <div> identificato dalla classe card-container, vedremo nel browser un rettangolo vuoto con i bordi arrotondati.

esempio slot Vue senza contenuto

Fra i tag di apertura e chiusura di uno <slot></slot> è però consentito disporre uno o più nodi che saranno poi visualizzati proprio nel caso in cui non viene iniettato nessun elemento nel componente.

Modifichiamo allora il componente <Card> come mostrato sotto.

// Card.vue
  <template>
  <div class="card-container">
    <slot>
      With no content, I am just an empty card 😞!
    </slot>
  </div>
</template>

Apriamo il file App.vue e rimuoviamo i diretti discendenti del componente <Card>.

// App.vue
<template>
  <Card />
</template>

<script>
import Card from './Card.vue';

export default {
  name: 'App',
  components: {
    Card
  }
}
</script>

A questo punto visualizzeremo il messaggio predefinito presente fra i tag <slot></slot> del template del file Card.vue

Slot multipli ed identificati da nome

In alcune situazioni può esserre utile poter iniettare degli elementi in diversi punti di un template. Ciò è particolarmente vero nei casi in cui si vuole definire una struttura base e consentire nello stesso momento di personalizzare le varie aree.

Per questo tipo di evenienze, Vue ha introdotto quelli che vengono definiti Named Slots. Come è facile intuire, si tratta di slot che sono identificati da un nome espresso tramite l’attributo name.

<template>
  <div class="card-container">
    <div class="top">
      <slot name="top"></slot>
    </div>
    <div class="main">
      <slot></slot>
    </div>
    <div class="call-to-action">
      <slot name="call-to-action"></slot>
    </div>
  </div>
</template>

Ciascun elemento <slot> è dunque identificabile attraverso un attributo name e quelli che non ne possiedono uno, come l’esempio visto in precedenza, hanno il nome implicito "default".

Così facendo siamo in grado di iniettare blocchi distinti di elementi in ciascun diverso segnaposto, usando la direttiva v-slot che verrà applicata ad un elemento <template>. A tale direttiva dovremo poi passare come argomento il nome dello slot di destinazione (useremo la sintassi v-slot:argument).

<template>
  <Card>
    <template v-slot:top>
      <h3>Free Plan</h3>
    </template>
    
    <ul>
      <li>Feature 1</li>
      <li>Feature 2</li>
      <li>Feature 3</li>
    </ul>

    <template v-slot:call-to-action>
      <button>Sign Up</button>
    </template>
  </Card>
</template>

Gli elementi che vogliamo iniettare nello slot predefinito non hanno bisogno di essere racchiusi all’interno di un elmento <template> con direttiva v-slot.

A partire dalla versione 2.6 di Vue.js è stata introdotta la funzione degli argomenti dinamici per le direttive. Ciò significa che anche per la direttiva v-slot è consentito usare un nome dinamico e quindi cambiare a run-time la destinazione in cui finiranno gli elementi iniettati.

<template>
  <Layout>
    <template v-slot:[componentProperty]>
      <h1>Title</h1>
    </template>
  </Layout>
</template>

Nell’esempio riportato sopra, a seconda del valore assunto da componentProperty, viene selezionato un differente slot (purché ne esista uno con quel nome) del componente Layout in cui andrà a finire l’elemento h1.

Scoped slots in Vue.js: cosa sono e perché usarli

Ricapitoliamo quanto visto finora sugli <slot>. Nel template di un componente <Child> inseriamo dei segnaposto attraverso uno o più elementi <slot></slot> che possono essere identificati tramite un attributo name.

Nel template di un componente <Parent>, in cui è presente il componente <Child>, inseriremo, fra i tag di apertura e chiusura di quest’ultimo, gli elementi che devono sostituire gli <slot></slot>.

All’interno del template del componente <Parent> avremo però accesso solo alle sue proprietà restituite dalla funzione data() dell’oggetto delle opzioni.

A volte, specie se si lavora con componenti di terze parti, può però essere utile accedere direttamente dal template del componente <Parent> alle proprietà definite nel componente <Child>. In questo modo potremo usare tali proprietà nel contenuto che inseriamo tra i tag di apertura e chiusura di <Parent>. Per questo tipo di situazioni esistono gli Scoped Slots, introdotti per consentire ad un componente di scegliere le proprietà accessibili dall’esterno attraverso la direttiva v-slot.

Supponiamo allora di avere un certo componente <Recipe> con una proprietà category a cui vogliamo accedere direttamente dal componente genitore (<List>) dal quale iniettiamo degli elementi nel componente figlio.

// Recipe.vue
<template>
  <div>
    <slot :category="category"></slot>
  </div>
</template>

<script>
export default {
  name: 'Recipe',
  data() {
    return {
      category: 'italian'
    }
  }
}
</script>

Per rendere visibile la proprietà category, utilizziamo la direttiva v-bind che applichiamo sull’elemento <slot></slot> (nell’esempio abbiamo usato la sintassi abbreviata ‘:’).

L’argomento passato alla direttiva v-bind costituisce il nome della proprietà accessibile nel componente genitore. Mentre fra doppie virgolette indichiamo la proprietà del componente <Recipe> che vogliamo associare. In questo caso i due nomi coincidono, ma avremmo potuto tranquillamente utilizzare un diverso nome per l’argomento della direttiva v-bind.

Nel componente genitore List potremo poi accedere alle proprietà esposte dal figlio attraverso il valore assegnato alla direttiva v-slot.

// List.vue
<template>
  <Recipe>
    <template v-slot:default="slotProps">
      <img 
        v-if="slotProps.category === 'italian'" 
        src="made-in-italy.jpeg" 
        alt="Made in italy">
    </template>
  </Recipe>
</template>

<script>
import Recipe from './Recipe.vue';

export default {
  name: 'List',
  components: {
    Recipe
  }
}
</script>

Dal componente <List> iniettiamo un’immagine nello slot predefinito di <Recipe> solo se il valore della proprietà category, esposta da <Recipe>, è pari ad ‘italian’.

Riepilogo

In questa lezione abbiamo illustrato il meccanismo degli slots con i quali ritagliamo uno spazio riservato agli elementi inseriti fra i tag di apertura e chiusura di un componente nel momento in cui questo sarà utilizzato all’interno del template di un altro componente. Abbiamo visto come creare più slot identificati da nomi, anche in modo dinamico, e abbiamo infine descritto il principio di funzionamento di quelli che Vue definisce scoped slots. Abbiamo visto che attraverso la direttiva v-bind, applicata su un elemento <slot>, possiamo rendere visibili alcune proprietà all’esterno. Un componente genitore potrà poi accedere a queste proprietà del figlio prelevando il valore della direttiva v-slot applicata su un elmento <template> il quale racchiude gli elementi che saranno iniettati al posto dello <slot> presente nel template del componente figlio.

Pubblicitร