Ciao, sono Francesco.
Programmo, insegno, bazzico e vivo tra Firenze e Prato.
Saturday, 27 February, 2021

Come funziona la composition API di Vue 3. La guida rapida e semplice - Parte 1

Se avete trovato questo post probabilmente amate Vue quanto lo amo io (♥)️ e allo stesso tempo siete rimasti un attimo destabilizzati (come lo sono rimasto io 🤣) dalla nuova “composition API” introdotta con Vue 3.

Dopo tanti anni che lavoriamo con la stessa metodologia non è sempre facile cambiare approccio (e nemmeno stare dietro a tutte le novità, parliamoci chiaro…) anche se spesso il risultato finale ripaga dello sforzo.

Vi spoilero subito il finale: la composition API è semplice e migliorerà il vostro codice, rendendolo più moderno e leggibile.

Non perdiamo tempo e vediamo cosa cambia da Vue 2. Finora abbiamo scritto il codice in questo modo:wq

<template>
  <div>
    <h1>{{ titolo }}</h1>
  </div>
</template>
<script>
  export default {
    data() {
      return {
        titolo: "Ciao carissimo, come stai? Buon anno nuovo"
      }
    }
  }
</script>

Dietro le quinte Vue prende tutte le proprietà contenute dentro data() e si appoggia al metodo Object.defineProperty() per creare i relativi getters e setters che terranno traccia dei vari cambiamenti.

Fino a qui, nulla di nuovo.

In Vue 3 il gioco cambia:

<template>
  <div>
    <h1>{{ titolo }}</h1>
  </div>
</template>
<script>
  export default {
    setup() {
      let titolo = "Sto benone, grazie. Auguri anche a te e famiglia"
    return { 
       titolo 
     }
    }
  }
</script>

Vediamo pezzo per pezzo:

  • data() è sostituto dal metodo setup() che viene eseguito prima della creazione del componente (e per questo motivo this dentro setup non è necessario e non funziona con la stessa logica di Vue 2)

  • la variabile titolo contiene una stringa semplice (non reattiva, dopo capirete il perché)

  • non “ritorniamo” immediatamente un oggetto come facevamo dentro data() ma scriviamo codice e logica (come avreste potuto fare in precedenza nel mounted() o nel created())

  • alla fine facciamo un return degli oggetti e delle funzioni a cui vogliamo accedere nel template (altrimenti non sarebbero disponibili)

Provate il codice, aprite i Vue DevTools e capirete che c’è qualcosa che non va. Titolo è una variabile semplice, non è reattiva. P-A-N-I-C-O 🙀

Per fortuna possiamo fixarla velocemente. Provate così:

<template>
  <div>
    <h1>{{ titolo }}</h1>
  </div>
</template>
<script>
  import { ref } from 'vue'
  
  export default {
    setup() {
      let titolo = ref("Fiuuuu, ora sono reattiva!")
      return { 
        titolo 
      }
    }
  }
</script>

ref() è un nuovo metodo in Vue 3 che prende un argomento e lo “wrappa” in un oggetto reattivo che a sua volta incapsula automaticamente una proprietà fondamentale: value (il valore stesso dell’oggetto).

Pausa.

Sotto con le domande e i dubbi. Credo di sapere a cosa state pensando.

Domande

Che tipo di valori accetta ref()?

Sicuramente tutti i primitivi: stringnumberBigInt

booleansymbolnull, undefined.

Fermo. E per oggetti e array che faccio?

Beh, per situazioni più “complesse” potete sostituire ref() con reactive():

<p>import { reactive } from 'vue'</p>

E lo usate così:

<p>let conteggio = reactive({val: 0}) // prova a stamparlo<br />
console.log(conteggio.val)</p>

☝ Reactive() è l’equivalente di Vue.observable() in Vue 2.x, è stato rinominato per evitare di fare confusione con i “RxJS observables”

Quindi devo per forza alternare ref() e reactive() a seconda del dato che voglio rendere reattivo?

Stessa cosa che ho pensato io. 🤣

In realtà ref accetta ogni tipo di dato (dietro le quinte, se lo usate per oggetti o array, chiama comunque reactive()), quindi potreste usarlo a priori… ma forse è più corretto separare la logica come ho indicato.

 OCCHIO che invece Reactive() non funziona con i dati primitivi

Prima hai detto che ref() contiene un “value” ma nel template non stai usando {{ titolo.value }}…quindi dov’è l’inghippo?

Nessun inghippo, value viene “unwrappato” automaticamente nel template da Vue 3 🙏

Quindi potete fare così…

<h1>{{ titolo }}</h1>

se lo passate dentro ref()

<p>let titolo = ref("Ciao Vue 3!")</p>

mentre ovviamente dovrete fare così…

<h1>{{ data.titolo }}</h1>

…se fa parte di un oggetto:

<p>let data = reactive({<br />
titolo: "Ciao, sono sempre Francesco"<br />
})</p>

Ancora una domanda: devo usare let o const per le variabili reattive?

Eh, bella domanda (che mi sono fatto da solo 🤣).

Vi direi che dipende un po’ da come scrivete il codice ma in realtà la risposta più corretta sarebbe di usare una const.

Mmm il valore muta, quindi meglio “let”. STAI TOPPANDO ALLA GRANDE.

Può darsi. Provate però a fare un console.log di una variabile reattiva:

<p>let titolo = ref("La reattività in Vue 3")<br />
console.log(titolo)</p>

Vi dovrebbe venire fuori qualcosa del genere:

Ref() prende un dato primitivo e ritorna una variabile reattiva e se la vuoi mutare devi usare titolo.value…ma siccome stiamo comunque parlando di un oggetto che non muterai mai nella sua completezza potete tranquillamente utilizzare const al posto di let.

Almeno questa è la spiegazione che mi sono dato io, poi ditemi la vostra :)

Tutto chiaro per ora? Vediamo un esempio leggermente più complesso.

Mettiamo caso che dobbiate sviluppare un blog e di conseguenza strutturare una single page.

<template>
<section class="min-h-screen flex flex-col items-center justify-center bg-gray-200 ">
  <div class="w-full max-w-3xl mb-12">
    <h2 class="text-3xl text-gray-900 tracking-tight font-extrabold">
      Le ultime dal blog
    </h2>
    <p class="mt-3 text-xl text-gray-500">
      Qui ci vorrei scrivere tanti post interessanti. Per ora c'è questo.
    </p>
  </div>
  <article class="w-full max-w-3xl bg-white p-6 rounded space-y-4 shadow-xl">
    <div class="flex space-x-2 items-center">
      <svg class="w-6 h-6 text-green-500" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM7 
9a1 1 0 100-2 1 1 0 000 2zm7-1a1 1 0 11-2 0 1 1 0 012 0zm-.464 5.535a1 1 0 10-1.415-1.414 
3 3 0 01-4.242 0 1 1 0 00-1.415 1.414 5 5 0 007.072 0z" clip-rule="evenodd"></path>
      </svg>
      <h1 class="text-xl text-gray-700 font-normal">
        {{ titolo }}
      </h1>
    </div>
    <div v-html="contenuto"></div>
    <span class="inline-flex items-center px-2.5 py-0.5 text-xs bg-green-100 text-green-800 rounded-full ">
      {{ conteggioCaratteri }} caratteri, tempo di lettura: ...
    </span>
  </article>
</section>
</template>
<script>
import {
  ref,
  computed
} from "vue"
export default {
  setup() {
    let titolo = ref("La reattività in Vue 3")
    let descrizione = ref("Qui ci va una splendida descrizione che bla bla bla...")
    let contenuto = ref("Lorem ipsum dolor sit amet, consectetur adipiscing elit....")
    let conteggioCaratteri = computed(() => contenuto.value.length)

    return {
      titolo,
      descrizione,
      contenuto,
      conteggioCaratteri
    }
  }
}
</script>

Questo è il risultato:

Il codice è facilmente leggibile e non introduce quasi nessuna novità rispetto a quanto già abbiamo visto.

L’unico dettaglio da notare riguarda la seguente riga:

<p>let conteggioCaratteri = computed(() => contenuto.value.length)</p>

☝ Attenzione, devi usare .value per conteggiare i caratteri di “contenuto”.

in questo caso il metodo computed() va importato perché non è disponibile di default dentro setup():

<p>import {<br />
ref,<br />
<strong>computed</strong><br />
} from "vue"</p>

Se vi torna meglio potete richiamare una funzione all’interno di computed() che si aggiornerà ogni qual volta contenuto cambierà di lunghezza:

<p>function calcolaLunghezza(){<br />
return contenuto.value.length<br />
}<br />
let conteggioCaratteri = computed(calcolaLunghezza)</p>

Tutto molto bello ma si può già migliorare un pelino.

<template>
<section class="min-h-screen flex flex-col items-center justify-center bg-gray-200 ">
  <div class="w-full max-w-3xl mb-12">
    <h2 class="text-3xl text-gray-900 tracking-tight font-extrabold">
      Le ultime dal blog
    </h2>
    <p class="mt-3 text-xl text-gray-500">
      Qui ci vorrei scrivere tanti post interessanti. Per ora c'è questo.
    </p>
  </div>
  <article class="w-full max-w-3xl bg-white p-6 rounded space-y-4 shadow-xl">
    <div class="flex space-x-2 items-center">
      <svg class="w-6 h-6 text-green-500" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM7 9a1 1 0 100-2 1 1 0
000 2zm7-1a1 1 0 11-2 0 1 1 0 012 0zm-.464 5.535a1 1 0 
10-1.415-1.414 3 3 0 01-4.242 0 1 1 0 00-1.415 1.414 5 5 0 007.072 0z" clip-rule="evenodd"></path>
      </svg>
      <h1 class="text-xl text-gray-700 font-normal">
        {{ post.titolo }}
      </h1>
    </div>
    <div v-html="post.contenuto"></div>
    <span class="inline-flex items-center px-2.5 py-0.5 text-xs bg-green-100 text-green-800 rounded-full ">
      {{ post.conteggioCaratteri }} caratteri, tempo di lettura: ...
    </span>
  </article>
</section>
</template>
<script>
import {
  reactive,
  computed
} from "vue"
export default {
  setup() {
    const post = reactive({
      titolo: "La reattività in Vue 3",
      descrizione: "",
      contenuto: "Ciao, sono cambiato",
      conteggioCaratteri: computed(calcolaLunghezza)
    })

    function calcolaLunghezza(){
      return post.contenuto.length
    }

    return { post }
  }
}
</script>

Cosa ho cambiato nella seconda versione:

  • non ho più 4 variabili let dentro setup() ma una sola const chiamata post. E’ un oggetto e di conseguenza non uso più ref() ma reactive()

  • ritorno una sola variabile nel template che esplodo con {{ post.titolo }}, {{ post.contenuto }} etc…

Ok, ma si può migliorare ancora un pochino con qualche altra novità?

Certo.

<template>
<section class="min-h-screen flex flex-col items-center justify-center bg-gray-200 ">
  <div class="w-full max-w-3xl mb-12">
    <h2 class="text-3xl text-gray-900 tracking-tight font-extrabold">
      Le ultime dal blog
    </h2>
    <p class="mt-3 text-xl text-gray-500">
      Qui ci vorrei scrivere tanti post interessanti. Per ora c'è questo.
    </p>
  </div>
  <article class="w-full max-w-3xl bg-white p-6 rounded space-y-4 shadow-xl">
    <div class="flex space-x-2 items-center">
      <svg class="w-6 h-6 text-green-500" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM7 9a1 1 
0 100-2 1 1 0 000 2zm7-1a1 1 0 11-2 0 1 1 0 012 0zm-.464 5.535a1 1 0 
10-1.415-1.414 3 3 0 01-4.242 0 1 1 0 00-1.415 1.414 5 5 0 007.072 0z" clip-rule="evenodd"></path>
      </svg>
      <h1 class="text-xl text-gray-700 font-normal">
        {{ titolo }}
      </h1>
    </div>
    <div v-html="contenuto"></div>
    <span class="inline-flex items-center px-2.5 py-0.5 text-xs bg-green-100 text-green-800 rounded-full ">
      {{ conteggioCaratteri }} caratteri, tempo di lettura: ...
    </span>
  </article>
</section>
</template>
<script>
import {
  reactive,
  computed,
  toRefs
} from "vue"
export default {
  setup() {
    const post = reactive({
      titolo: "La reattività in Vue 3",
      descrizione: "",
      contenuto: "Ciao, sono cambiato",
      conteggioCaratteri: computed(() => post.contenuto.length)
    })
    return { ...toRefs(post) }
  }
}
</script>

Cosa ho cambiato nella terza versione:

  • con reactive() siamo obbligati a esportare l’intero oggetto post e siamo costretti ad accedere alle singole proprietà usando la sintassi che abbiamo già visto: {{ post.titolo }}. Per ovviare a questo problema (che poi è una preferenza personale, un modello mentale) puoi importare il metodo toRefs() che mantiene la reattività dell’oggetto anche usando l’operatore spread (➜ …toRefs(post) ). Di conseguenza ora puoi scrivere {{ titolo }} nel tuo template e mantenere una sintassi più pulita.

Per ora è tutto (anche se non è tutto ovviamente, c’è ancora da pedalare 🤣). Già così abbiamo messo tanta carne al fuoco.

Vi consiglio di fare qualche prova e poi ci risentiamo per la seconda parte 👋

P.s scambiamoci qualche feedback anche su Twitter si vi aggrada :)

Scrivimi un messaggio su questo pezzo