La nuova Composition API di Vue3, cos'è e perchè è importante

May 3rd, 2020 - 2 min read

Nei giorni scorsi ho dato un'occhiata a vue3 - che nel momento in cui scrivo è ancora in beta - e in particolare a un modo nuovo di organizzare i nostri Single File Components, chiamato Composition API.

In sostanza questa API espone alcune funzioni del core di Vue per poterle utilizzare in normalissime funzioni javascript e poterle a loro volta riutilizzare nelle nostre applicazioni. Se state pensando agli Hooks di React avete capito perfettamente di cosa stiamo parlando.

La Composition API - Evan You la chiama VCA - ci aiuta a organizzare la logica di un componente che invece sarebbe separata dalle varie funzioni di un ciclo di vita di un componente (created, mounted etc) oppure dalle proprietà stesse del nostro componente (methods, watch, computed)

VCA in pratica

Il metodo più facile per provare Vue3 oggi è utilizzare Vite e l'esempio più classico è componente con un contatore numerico, e il pulsante per incrementare il counter. Ci sono migliaia di modi di implementare un componente di questo tipo, chi si avvicina a librerie come Vue o React lo ha sicuramente visto nel primo tutorial.

La logica è sostanzialmente sempre la stessa ma la forma, il template, cambia continuamente e quindi come si fa? Fino ad oggi le opzioni erano piuttosto limitate: utilizzare dei mixins e cambiare il template o scrivere renderless components utilizzando gli scoped slots di vue.

La nuova VCA permette di trasformare questo:

<template>
    <div>  
        count is: {{count}}
        <button @click="increment">increment</button>  
    </div>
</template>
<script>
export default {
    data() {
        return {
            count: 0;
        }
    },
    methods: {
        increment() {
            this.count++;
        }
    }

}
</script>

in questo

<template>
  <div>
    Count is: {{ count }}
    <button @click="increment">increment</button>  
  </div>
</template>

<script>
import useCount from './useCount.js'

export default {
  setup() {
    const { count, increment } = useCount()

    return {
      count,
      increment,
    }
  }
}
</script>

Che differenza fa? che nel secondo esempio la logica del componente sta tutta da un'altra parte, ogni volta che mi servirà un counter dovrò semplicemente "usare" la logica della funzione useCount, senza bisogno di riscriverla. L'esempio naturalmente è molto semplice e limitato, ma provate ad immaginare altre funzionalità più succulente: useLocalStorage, useStore, useFirebase... tanto per volare con la fantasia.

Tutto bello, ma... com'è fatto questo useCount?

Ci arriviamo subito, prima però proviamo ad immaginarlo! Nel mio componente importo la funzione useCount che evidentemente ritorna un oggetto che viene poi destrutturato - tramite parentesi graffe - in due variabili, count e increment. Una è il nostro contatore, l'altra la funzione che chiamiamo nel template. La nostra funzione useCount avrà quindi un contatore, una funzione increment e dovrà ritornare un oggetto, giusto?

import { reactive, toRefs } from 'vue'

export default function useCount() {

  const state = reactive({
    count:0,
  })

  function increment() {
    state.count++
  }

  return { 
    ...toRefs(state),
    increment
  }
}

Ecco qui la nostra funzione useCount che come previsto ha un contatore, una funzione increment e li restituisce dentro un oggetto. In mezzo ci sono però delle cose non ancora chiare, che cosa sono quelle funzioni "reactive" e toRefs? Si tratta di quelle funzioni del core di Vue3 che ci vengono messe a disposizione per estrarre la nostra logica. Se vogliamo la "reattività" di Vue non basta creare la variabile, dobbiamo fare in modo che ogni volta che ne cambiamo il valore, la nostra vista - quindi il dom - cambi di conseguenza.

Questa è una cosa molto importante ed è la chiave per comprendere la nuova composition API. Quando inizializziamo una variable dobbiamo farla passare per una funzione che permetta a Vue di reagire al cambiamento. Questa funzione in javascript si chiama Proxy e vue ne ha di due tipi: ref e reactive. La prima è perfetta per le primitive (numeri, stringhe, etc), la seconda per oggetti e array. Avrei quindi potuto dichiarare così la mia variable count:

const count = ref(0)

ma avrei dovuto cambiare di conseguenza il metodo increment:

function increment() {
    state.count.value++
  }

La differenza è puramente stilistica, non esiste al momento un consenso su quale sia la scrittura migliore.

e toRefs?

Utilizzare la funzione reactive è lo stile che richiede meno "fatica" a livello mentale ma presenta dei problemi se l'oggetto ritornato viene destrutturato. La reattività è completamente persa e dobbiamo quindi ovviare ritonando i singoli valori come refs, attraverso il metodo toRefs.

Per approfondire rimando al paragrafo Ref vs Reactive della RFC da cui ho tratto la mia spiegazione.

Spero di aver sciolto i nodi principali e di avervi invogliato a provare la nuova composition API, scrivetemi su twitter per i vostri feedback e segnalazioni.

© 2020 Marco Bonomo