Cosa c'è di nuovo in Vue 3?

Pubblicato: 2022-03-10
Riassunto veloce ↬ Vue 3 include molte nuove interessanti funzionalità e modifiche ad alcune di quelle esistenti che mirano a rendere lo sviluppo con il framework molto più semplice e manutenibile. In questo articolo, daremo un'occhiata ad alcune di queste nuove funzionalità e come iniziare con esse. Daremo anche un'occhiata ad alcune delle modifiche apportate alle funzionalità esistenti.

Con il rilascio di Vue 3, gli sviluppatori devono effettuare l'aggiornamento da Vue 2 in quanto viene fornito con una manciata di nuove funzionalità che sono super utili nella creazione di componenti facili da leggere e manutenibili e modi migliorati per strutturare la nostra applicazione in Vue. Daremo un'occhiata ad alcune di queste funzionalità in questo articolo.

Alla fine di questo tutorial, i lettori lo faranno;

  1. Conoscere provide / inject e come usarlo.
  2. Avere una conoscenza di base di Teletrasporto e come usarlo.
  3. Conoscere i frammenti e come utilizzarli.
  4. Conoscere le modifiche apportate all'API Global Vue.
  5. Conoscere le modifiche apportate all'API Events.

Questo articolo è rivolto a coloro che hanno una corretta comprensione di Vue 2.x. Puoi trovare tutto il codice utilizzato in questo esempio in GitHub.

provide / inject

In Vue 2.x, avevamo degli props di scena che semplificavano il passaggio di dati (stringhe, array, oggetti, ecc.) da un componente padre direttamente al suo componente figlio. Ma durante lo sviluppo, abbiamo spesso riscontrato casi in cui era necessario passare i dati dal componente padre a un componente profondamente nidificato che era più difficile da fare con gli props di scena. Ciò ha comportato l'uso di Vuex Store, Event Hub e, talvolta, il passaggio di dati attraverso i componenti profondamente nidificati. Diamo un'occhiata a una semplice app;

È importante notare che Vue 2.2.0 includeva anche provide / inject che non era consigliato utilizzare nel codice dell'applicazione generico.

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { color: "", colors: ["red", "blue", "green"], }; }, }; </script>
 # childComponent.vue <template> <div class="hello"> <h1>{{ msg }}</h1> <color-selector :color="color"></color-selector> </div> </template> <script> import colorSelector from "@/components/colorComponent.vue"; export default { name: "HelloWorld", components: { colorSelector, }, props: { msg: String, color: String, }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
 # colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { props: { color: String, }, }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

Qui abbiamo una pagina di destinazione con un menu a discesa contenente un elenco di colori e stiamo passando il color selezionato a childComponent.vue come supporto. Questo componente figlio ha anche un prop msg che accetta un testo da visualizzare nella sezione del modello. Infine, questo componente ha un componente figlio ( colorComponent.vue ) che accetta un color prop dal componente genitore che viene utilizzato per determinare la classe per il testo in questo componente. Questo è un esempio di passaggio di dati attraverso tutti i componenti.

Ma con Vue 3, possiamo farlo in modo più semplice e veloce utilizzando la nuova coppia Fornire e iniettare. Come suggerisce il nome, usiamo provide come una funzione o un oggetto per rendere disponibili i dati da un componente padre a uno qualsiasi dei suoi componenti nidificati, indipendentemente dalla profondità di nidificazione di tale componente. Utilizziamo il modulo dell'oggetto quando passiamo valori codificati per provide in questo modo;

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { colors: ["red", "blue", "green"], }; }, provide: { color: 'blue' } }; </script>

Ma per le istanze in cui è necessario passare una proprietà dell'istanza del componente per provide , utilizziamo la modalità funzione in modo che ciò sia possibile;

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "blue", colors: ["red", "blue", "green"], }; }, provide() { return { color: this.selectedColor, }; }, }; </script>

Dal momento che non abbiamo bisogno degli oggetti di scena del color sia in childComponent.vue che colorComponent.vue , ce ne stiamo sbarazzando. L'aspetto positivo dell'utilizzo di provide è che il componente padre non ha bisogno di sapere quale componente ha bisogno della proprietà che sta fornendo.

Per utilizzarlo nel componente che ne ha bisogno in questo caso, colorComponent.vue , lo facciamo;

 # colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

Qui, usiamo inject che accetta una matrice delle variabili richieste di cui il componente ha bisogno. In questo caso, abbiamo solo bisogno della proprietà color , quindi la passiamo solo. Dopodiché, possiamo usare il color nello stesso modo in cui lo usiamo quando usiamo gli oggetti di scena.

Potremmo notare che se proviamo a selezionare un nuovo colore utilizzando il menu a discesa, il colore non si aggiorna in colorComponent.vue e questo perché per impostazione predefinita le proprietà provide non sono reattive. Per risolvere questo problema, utilizziamo il metodo computed .

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; import { computed } from "vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "", todos: ["Feed a cat", "Buy tickets"], colors: ["red", "blue", "green"], }; }, provide() { return { color: computed(() => this.selectedColor), }; }, }; </script>

Qui, importiamo computed e passiamo il nostro selectedColor in modo che possa essere reattivo e aggiornato quando l'utente seleziona un colore diverso. Quando si passa una variabile al metodo calcolato, restituisce un oggetto che ha un value . Questa proprietà contiene il valore della tua variabile, quindi per questo esempio, dovremmo aggiornare colorComponent.vue in modo che assomigli a questo;

 # colorComponent.vue <template> <p :class="[color.value]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

Qui, cambiamo il color in color.value per rappresentare la modifica dopo aver reso il color reattivo utilizzando il metodo computed . A questo punto, la class del testo in questo componente cambia sempre ogni volta che selectedColor cambia nel componente padre.

Altro dopo il salto! Continua a leggere sotto ↓

Teletrasporto

Ci sono casi in cui creiamo componenti e li posizioniamo in una parte della nostra applicazione a causa della logica utilizzata dall'app, ma sono destinati a essere visualizzati in un'altra parte della nostra applicazione. Un esempio comune di questo potrebbe essere un modale o un popup che ha lo scopo di visualizzare e coprire l'intero schermo. Mentre possiamo creare una soluzione alternativa per questo usando la proprietà position di CSS su tali elementi, con Vue 3, possiamo anche fare usando Teleport.

Teletrasporto ci consente di rimuovere un componente dalla sua posizione originale in un documento, dal contenitore predefinito #app in cui sono racchiuse le app Vue e spostarlo su qualsiasi elemento esistente nella pagina in cui viene utilizzato. Un buon esempio potrebbe essere l'utilizzo di Teletrasporto per spostare un componente di intestazione dall'interno del div #app a header . È importante notare che puoi teletrasportarti solo su elementi che esistono al di fuori del Vue DOM.

messaggio di errore nella console quando ti teletrasporti su un elemento non valido
Messaggio di errore di teletrasporto nella console: messaggio di errore di destinazione del teletrasporto non valido nel terminale. (Grande anteprima)

Il componente Teleport accetta due oggetti di scena che determinano il comportamento di questo componente e lo sono;

  1. to
    Questo prop accetta un nome di classe, un id, un elemento o un attributo data-*. Possiamo anche rendere dinamico questo valore passando a :to prop invece to e modificando dinamicamente l'elemento Teleport.
  2. :disabled
    Questo oggetto accetta un Boolean e può essere utilizzato per attivare o disattivare la funzione Teletrasporto su un elemento o un componente. Questo può essere utile per modificare dinamicamente la posizione di un elemento.

Un esempio ideale dell'utilizzo di Teleport è simile a questo;

 # index.html** <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <title> <%= htmlWebpackPlugin.options.title %> </title> </head> <!-- add container to teleport to --> <header class="header"></header> <body> <noscript> <strong >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript> <div></div> <!-- built files will be auto injected --> </body> </html>

Nel file index.html predefinito nella tua app Vue, aggiungiamo un elemento di header perché vogliamo teletrasportare il nostro componente di intestazione a quel punto nella nostra app. Abbiamo anche aggiunto una classe a questo elemento per lo stile e per un facile riferimento nel nostro componente Teletrasporto.

 # Header.vue** <template> <teleport to="header"> <h1 class="logo">Vue 3 </h1> <nav> <router-link to="/">Home</router-link> </nav> </teleport> </template> <script> export default { name: "app-header", }; </script> <style> .header { display: flex; align-items: center; justify-content: center; } .logo { margin-right: 20px; } </style>

Qui creiamo il componente di intestazione e aggiungiamo un logo con un collegamento alla home page della nostra app. Aggiungiamo anche il componente Teleport e diamo to prop un valore di header perché vogliamo che questo componente visualizzi all'interno di questo elemento. Infine, importiamo questo componente nella nostra app;

 # App.vue <template> <router-view /> <app-header></app-header> </template> <script> import appHeader from "@/components/Header.vue"; export default { components: { appHeader, }, }; </script>

In questo file, importiamo il componente dell'intestazione e lo posizioniamo nel modello in modo che possa essere visibile nella nostra app.

Ora, se ispezioniamo l'elemento della nostra app, noteremo che il nostro componente di intestazione è all'interno dell'elemento di header ;

Componente di intestazione in DevTools
Componente di intestazione in DevTools (anteprima grande)

Frammenti

Con Vue 2.x, era impossibile avere più elementi radice nel template del file e, come soluzione alternativa, gli sviluppatori hanno iniziato a racchiudere tutti gli elementi in un elemento padre. Anche se questo non sembra un problema serio, ci sono casi in cui gli sviluppatori vogliono eseguire il rendering di un componente senza un contenitore che avvolge tali elementi, ma devono accontentarsi di quello.

Con Vue 3 è stata introdotta una nuova funzionalità chiamata Frammenti e questa funzionalità consente agli sviluppatori di avere più elementi nel proprio file modello radice. Quindi, con Vue 2.x, ecco come apparirà un componente del contenitore del campo di input;

 # inputComponent.vue <template> <div> <label :for="label">label</label> <input :type="type" : :name="label" /> </div> </template> <script> export default { name: "inputField", props: { label: { type: String, required: true, }, type: { type: String, required: true, }, }, }; </script> <style></style>

Qui, abbiamo un semplice componente dell'elemento del modulo che accetta due prop, label e type , e la sezione del modello di questo componente è racchiusa in un div. Questo non è necessariamente un problema, ma se desideri che l'etichetta e il campo di input siano direttamente all'interno dell'elemento del form . Con Vue 3, gli sviluppatori possono facilmente riscrivere questo componente in modo che assomigli a questo;

 # inputComponent.vue <template class="testingss"> <label :for="label">{{ label }}</label> <input :type="type" : :name="label" /> </template>

Con un singolo nodo radice, gli attributi vengono sempre attribuiti al nodo radice e sono anche noti come Attributi non prop . Sono eventi o attributi passati a un componente che non ha proprietà corrispondenti definite in props o emits . Esempi di tali attributi sono class e id . Tuttavia, è necessario definire in modo esplicito a quale degli elementi in un componente del nodo multi-radice deve essere attribuito.

Ecco cosa significa usare inputComponent.vue dall'alto;

  1. Quando si aggiunge class a questo componente nel componente padre, è necessario specificare a quale componente verrebbe attribuita questa class , altrimenti l'attributo non ha effetto.
 <template> <div class="home"> <div> <input-component class="awesome__class" label="name" type="text" ></input-component> </div> </div> </template> <style> .awesome__class { border: 1px solid red; } </style>

Quando fai qualcosa del genere senza definire dove dovrebbero essere attribuiti gli attributi, ricevi questo avviso nella tua console;

messaggio di errore nel terminale quando gli attributi non sono distribuiti
Messaggio di errore nel terminale quando gli attributi non sono distribuiti (anteprima grande)

E il border non ha effetto sul componente;

componente senza distribuzione degli attributi
Componente senza distribuzione degli attributi (anteprima grande)
  1. Per risolvere questo problema, aggiungi un v-bind="$attrs" sull'elemento a cui desideri distribuire tali attributi;
 <template> <label :for="label" v-bind="$attrs">{{ label }}</label> <input :type="type" : :name="label" /> </template>

Qui, stiamo dicendo a Vue che vogliamo che gli attributi siano distribuiti all'elemento label , il che significa che vogliamo che la awesome__class venga applicata ad esso. Ora, se ispezioniamo il nostro elemento nel browser, vedremmo che la classe è stata aggiunta label e quindi un bordo è ora attorno all'etichetta.

componente con distribuzione degli attributi
Componente con distribuzione degli attributi (Anteprima grande)

API globale

Non era raro vedere Vue.component o Vue.use nel file main.js di un'applicazione Vue. Questi tipi di metodi sono noti come API globali e ce ne sono molti in Vue 2.x. Una delle sfide di questo metodo è che rende impossibile isolare determinate funzionalità in un'istanza della tua app (se hai più di un'istanza nella tua app) senza che ciò influisca sulle altre app perché sono tutte montate su Vue. Questo è ciò che intendo;

 Vue.directive('focus', { inserted: el => el.focus() }) Vue.mixin({ /* ... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })

Per il codice sopra, è impossibile affermare che la Direttiva Vue sia associata ad app1 e Mixin ad app2 ma invece sono entrambi disponibili nelle due app.

Vue 3 viene fornito con una nuova API globale nel tentativo di risolvere questo tipo di problema con l'introduzione di createApp . Questo metodo restituisce una nuova istanza di un'app Vue. Un'istanza dell'app espone un sottoinsieme delle API globali correnti. Con questo, tutte le API (componente, mixin, direttiva, uso, ecc.) che mutano Vue da Vue 2.x verranno ora spostate in singole istanze dell'app e ora ogni istanza della tua app Vue può avere funzionalità uniche per senza influire su altre app esistenti.

Ora, il codice sopra può essere riscritto come;

 const app1 = createApp({}) const app2 = createApp({}) app1.directive('focus', { inserted: el => el.focus() }) app2.mixin({ /* ... */ })

È tuttavia possibile creare funzionalità che si desidera condividere tra tutte le app e ciò può essere fatto utilizzando una funzione di fabbrica.

API degli eventi

Uno dei modi più comuni adottati dagli sviluppatori per il passaggio di dati tra componenti che non hanno una relazione da genitore a figlio oltre all'utilizzo di Vuex Store è l'uso di Event Bus. Uno dei motivi per cui questo metodo è comune è perché è facile iniziare con esso;

 # eventBus.js const eventBus = new Vue() export default eventBus;

Successivamente, la cosa successiva sarebbe importare questo file in main.js per renderlo disponibile a livello globale nella nostra app o importarlo nei file di cui hai bisogno;

 # main.js import eventBus from 'eventBus' Vue.prototype.$eventBus = eventBus

Ora puoi emettere eventi e ascoltare eventi emessi come questo;

 this.$eventBus.$on('say-hello', alertMe) this.$eventBus.$emit('pass-message', 'Event Bus says Hi')

C'è un sacco di codebase Vue che è pieno di codice come questo. Tuttavia, con Vue 3, sarebbe impossibile farlo perché $on , $off e $once sono stati tutti rimossi ma $emit è ancora disponibile perché è necessario che il componente figlio emetta eventi ai componenti padre. Un'alternativa a questo sarebbe l'utilizzo provide / inject o una qualsiasi delle librerie di terze parti consigliate.

Conclusione

In questo articolo, abbiamo spiegato come passare i dati da un componente padre a un componente figlio profondamente nidificato utilizzando la coppia provide / inject . Abbiamo anche esaminato come riposizionare e trasferire i componenti da un punto all'altro della nostra app. Un'altra cosa che abbiamo esaminato è il componente del nodo multi-root e come assicurarci di distribuire gli attributi in modo che funzionino correttamente. Infine, abbiamo anche trattato le modifiche all'API Events e all'API globale.

Ulteriori risorse

  • "Funzioni JavaScript Factory con ES6+", Eric Elliott, Medium
  • "Utilizzo di Event Bus per condividere oggetti di scena tra i componenti Vue", Kingsley Silas, CSS-Tricks
  • Utilizzando più teletrasporti sullo stesso obiettivo, Vue.js Docs
  • Attributi non prop, documenti Vue.js
  • Lavorare con la reattività, Vue.js Docs
  • teleport , Vue.js Docs
  • Frammenti, documenti Vue.js
  • Sintassi 2.x, documenti Vue.js