Cosa c'è di nuovo in Vue 3?
Pubblicato: 2022-03-10Con 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;
- Conoscere
provide / inject
e come usarlo. - Avere una conoscenza di base di Teletrasporto e come usarlo.
- Conoscere i frammenti e come utilizzarli.
- Conoscere le modifiche apportate all'API Global Vue.
- 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.
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.


Il componente Teleport accetta due oggetti di scena che determinano il comportamento di questo componente e lo sono;
-
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 inveceto
e modificando dinamicamente l'elemento Teleport. -
:disabled
Questo oggetto accetta unBoolean
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
;

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;
- Quando si aggiunge
class
a questo componente nel componente padre, è necessario specificare a quale componente verrebbe attribuita questaclass
, 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;

E il border
non ha effetto sul componente;

- 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.

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