Was ist neu in Vue 3?
Veröffentlicht: 2022-03-10Mit der Veröffentlichung von Vue 3 müssen Entwickler das Upgrade von Vue 2 vornehmen, da es eine Handvoll neuer Funktionen enthält, die beim Erstellen von leicht lesbaren und wartbaren Komponenten und verbesserten Möglichkeiten zur Strukturierung unserer Anwendung in Vue sehr hilfreich sind. Wir werden uns einige dieser Funktionen in diesem Artikel ansehen.
Am Ende dieses Tutorials werden die Leser;
- Informieren Sie sich über
provide / inject
und wie Sie es verwenden. - Haben Sie ein grundlegendes Verständnis von Teleport und wie man es benutzt.
- Informieren Sie sich über Fragmente und deren Verwendung.
- Informieren Sie sich über die Änderungen, die an der Global Vue API vorgenommen wurden.
- Informieren Sie sich über die an der Events-API vorgenommenen Änderungen.
Dieser Artikel richtet sich an diejenigen, die ein angemessenes Verständnis von Vue 2.x haben. Den gesamten in diesem Beispiel verwendeten Code finden Sie auf GitHub.
provide / inject
In Vue 2.x hatten wir props
, die es einfach machten, Daten (Strings, Arrays, Objekte usw.) von einer übergeordneten Komponente direkt an ihre untergeordnete Komponente zu übergeben. Aber während der Entwicklung fanden wir oft Fälle, in denen wir Daten von der übergeordneten Komponente an eine tief verschachtelte Komponente übergeben mussten, was mit props
schwieriger zu bewerkstelligen war. Dies führte zur Verwendung von Vuex Store, Event Hub und manchmal zum Weiterleiten von Daten durch die tief verschachtelten Komponenten. Schauen wir uns eine einfache App an;
Es ist wichtig zu beachten, dass Vue 2.2.0 auch mit provide / inject
geliefert wurde, dessen Verwendung in generischem Anwendungscode nicht empfohlen wurde.
# 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>
Hier haben wir eine Zielseite mit einem Dropdown-Menü, das eine Liste von Farben enthält, und wir übergeben die ausgewählte color
als Requisite an childComponent.vue
. Diese untergeordnete Komponente hat auch eine msg
-Prop, die einen Text akzeptiert, der im Vorlagenabschnitt angezeigt werden soll. Schließlich hat diese Komponente eine untergeordnete Komponente ( colorComponent.vue
), die eine color
von der übergeordneten Komponente akzeptiert, die zum Bestimmen der Klasse für den Text in dieser Komponente verwendet wird. Dies ist ein Beispiel für die Weitergabe von Daten durch alle Komponenten.
Aber mit Vue 3 können wir dies auf sauberere und kürzere Weise tun, indem wir das neue Paar „Provide“ und „Inject“ verwenden. Wie der Name schon sagt, verwenden wir „ provide
“ entweder als Funktion oder als Objekt, um Daten von einer übergeordneten Komponente für jede ihrer verschachtelten Komponenten verfügbar zu machen, unabhängig davon, wie tief eine solche Komponente verschachtelt ist. Wir verwenden das Objektformular, wenn wir hartcodierte Werte übergeben, provide
diese bereitzustellen;
# 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>
Aber für Fälle, in denen Sie eine Komponenteninstanzeigenschaft übergeben müssen, um provide
, verwenden wir den Funktionsmodus, damit dies möglich ist;
# 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>
Da wir die color
in childComponent.vue
und colorComponent.vue
nicht benötigen, werden wir sie los. Das Gute an der Verwendung von provide
ist, dass die übergeordnete Komponente nicht wissen muss, welche Komponente die von ihr bereitgestellte Eigenschaft benötigt.
Um dies in der Komponente zu nutzen, die es in diesem Fall benötigt, colorComponent.vue
, tun wir dies;
# 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>
Hier verwenden wir inject
, das ein Array der erforderlichen Variablen aufnimmt, die die Komponente benötigt. In diesem Fall benötigen wir nur die color
, also übergeben wir nur diese. Danach können wir die color
genauso verwenden, wie wir sie bei der Verwendung von Requisiten verwenden.
Wir stellen möglicherweise fest, dass die Farbe in colorComponent.vue
nicht aktualisiert wird, wenn wir versuchen, eine neue Farbe über das Dropdown auszuwählen, und dies liegt daran, dass die Eigenschaften in der provide
standardmäßig nicht reaktiv sind. Um das zu beheben, verwenden wir die 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>
Hier importieren wir computed
und übergeben unsere selectedColor
, damit sie reaktiv sein und aktualisiert werden kann, wenn der Benutzer eine andere Farbe auswählt. Wenn Sie eine Variable an die berechnete Methode übergeben, gibt sie ein Objekt zurück, das einen value
hat. Diese Eigenschaft enthält den Wert Ihrer Variablen. Für dieses Beispiel müssten wir also colorComponent.vue
so aktualisieren, dass es so aussieht.
# 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>
Hier ändern wir color
in color.value
, um die Änderung darzustellen, nachdem color
mit der computed
Methode reaktiv gemacht wurde. An diesem Punkt würde sich die class
des Textes in dieser Komponente immer dann ändern, wenn selectedColor
sich in der Elternkomponente ändert.
Teleportieren
Es gibt Fälle, in denen wir Komponenten erstellen und sie aufgrund der von der App verwendeten Logik in einem Teil unserer Anwendung platzieren, aber dafür vorgesehen sind, in einem anderen Teil unserer Anwendung angezeigt zu werden. Ein gängiges Beispiel hierfür wäre ein Modal oder ein Popup, das den gesamten Bildschirm anzeigen und abdecken soll. Während wir hierfür eine Problemumgehung erstellen können, indem wir die position
von CSS für solche Elemente verwenden, können wir mit Vue 3 auch Teleport verwenden.
Teleport ermöglicht es uns, eine Komponente aus ihrer ursprünglichen Position in einem Dokument aus dem standardmäßigen #app
Container zu entfernen, in den Vue-Apps eingeschlossen sind, und sie zu einem beliebigen vorhandenen Element auf der Seite zu verschieben, auf der sie verwendet wird. Ein gutes Beispiel wäre die Verwendung von Teleport, um eine Header-Komponente aus dem #app
Div in einen header
zu verschieben. Es ist wichtig zu beachten, dass Sie nur zu Elementen teleportieren können, die außerhalb des Vue-DOM vorhanden sind.


Die Teleport-Komponente akzeptiert zwei Requisiten, die das Verhalten dieser Komponente bestimmen, und das sind sie;
-
to
Diese Prop akzeptiert entweder einen Klassennamen, eine ID, ein Element oder ein data-*-Attribut. Wir können diesen Wert auch dynamisch machen, indem wir eine:to
-Prop im Gegensatz zuto
und das Teleport-Element dynamisch ändern. -
:disabled
Diese Requisite akzeptiert einenBoolean
und kann verwendet werden, um die Teleport-Funktion für ein Element oder eine Komponente umzuschalten. Dies kann nützlich sein, um die Position eines Elements dynamisch zu ändern.
Ein ideales Beispiel für die Verwendung von Teleport sieht so aus;
# 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>
In der Standarddatei index.html
in Ihrer Vue-App fügen wir ein header
-Element hinzu, weil wir unsere Header-Komponente zu diesem Punkt in unserer App teleportieren möchten. Wir haben diesem Element auch eine Klasse zum Stylen und zur einfachen Referenzierung in unserer Teleport-Komponente hinzugefügt.
# 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>
Hier erstellen wir die Header-Komponente und fügen ein Logo mit einem Link zur Homepage unserer App hinzu. Wir fügen auch die Teleport-Komponente hinzu und geben der to
-Prop den Wert header
, weil wir möchten, dass diese Komponente in diesem Element gerendert wird. Schließlich importieren wir diese Komponente in unsere App;
# App.vue <template> <router-view /> <app-header></app-header> </template> <script> import appHeader from "@/components/Header.vue"; export default { components: { appHeader, }, }; </script>
In diese Datei importieren wir die Header-Komponente und platzieren sie in der Vorlage, damit sie in unserer App sichtbar ist.
Wenn wir nun das Element unserer App untersuchen, würden wir feststellen, dass sich unsere Header-Komponente innerhalb des header
Elements befindet;

Fragmente
Mit Vue 2.x war es unmöglich, mehrere Root-Elemente in der template
Ihrer Datei zu haben, und als Workaround begannen die Entwickler, alle Elemente in ein übergeordnetes Element zu packen. Dies scheint zwar kein ernstes Problem zu sein, aber es gibt Fälle, in denen Entwickler eine Komponente rendern möchten, ohne dass ein Container solche Elemente umgibt, sich aber damit begnügen müssen.
Mit Vue 3 wurde eine neue Funktion namens Fragmente eingeführt, und diese Funktion ermöglicht es Entwicklern, mehrere Elemente in ihrer Stammvorlagendatei zu haben. Mit Vue 2.x würde also eine Eingabefeld-Container-Komponente so aussehen;
# 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>
Hier haben wir eine einfache Formularelementkomponente, die zwei Requisiten akzeptiert, label
und type
, und der Vorlagenabschnitt dieser Komponente ist in ein div eingeschlossen. Dies ist nicht unbedingt ein Problem, aber wenn Sie möchten, dass sich die Beschriftung und das Eingabefeld direkt in Ihrem form
befinden. Mit Vue 3 können Entwickler diese Komponente einfach so umschreiben, dass sie so aussieht;
# inputComponent.vue <template class="testingss"> <label :for="label">{{ label }}</label> <input :type="type" : :name="label" /> </template>
Bei einem einzelnen Wurzelknoten werden Attribute immer dem Wurzelknoten zugeordnet und sie werden auch als Non-Prop-Attribute bezeichnet . Sie sind Ereignisse oder Attribute, die an eine Komponente übergeben werden, für die keine entsprechenden Eigenschaften in props
oder emits
definiert sind. Beispiele für solche Attribute sind class
und id
. Es ist jedoch erforderlich, explizit zu definieren, welchem der Elemente in einer Multi-Root-Knotenkomponente zugeordnet werden soll.
Folgendes bedeutet die Verwendung von inputComponent.vue
von oben;
- Beim Hinzufügen von
class
zu dieser Komponente in der übergeordneten Komponente muss angegeben werden, welcher Komponente dieseclass
zugeordnet werden soll, sonst hat das Attribut keine Wirkung.
<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>
Wenn Sie so etwas tun, ohne zu definieren, wo die Attribute zugeordnet werden sollen, erhalten Sie diese Warnung in Ihrer Konsole;

Und die border
hat keine Auswirkung auf die Komponente;

- Um dies zu beheben, fügen Sie dem Element, an das solche Attribute verteilt werden sollen, ein
v-bind="$attrs"
;
<template> <label :for="label" v-bind="$attrs">{{ label }}</label> <input :type="type" : :name="label" /> </template>
Hier teilen wir Vue mit, dass die Attribute an das label
-Element verteilt werden sollen, was bedeutet, dass die awesome__class
darauf angewendet werden soll. Wenn wir nun unser Element im Browser untersuchen, sehen wir, dass die Klasse jetzt zum label
hinzugefügt wurde und daher jetzt ein Rahmen um das Label verläuft.

Globale API
Es war nicht ungewöhnlich, Vue.component
oder Vue.use
in der main.js
-Datei einer Vue-Anwendung zu sehen. Diese Arten von Methoden sind als globale APIs bekannt und es gibt eine ganze Reihe davon in Vue 2.x. Eine der Herausforderungen dieser Methode besteht darin, dass es unmöglich ist, bestimmte Funktionen auf eine Instanz Ihrer App zu isolieren (wenn Sie mehr als eine Instanz in Ihrer App haben), ohne dass dies Auswirkungen auf andere Apps hat, da sie alle auf Vue gemountet sind. Das ist was ich meine;
Vue.directive('focus', { inserted: el => el.focus() }) Vue.mixin({ /* ... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })
Für den obigen Code ist es unmöglich zu sagen, dass die Vue-Direktive mit app1
und das Mixin mit app2
, aber stattdessen sind beide in den beiden Apps verfügbar.
Vue 3 wird mit einer neuen globalen API geliefert, um diese Art von Problem mit der Einführung von createApp
zu beheben. Diese Methode gibt eine neue Instanz einer Vue-App zurück. Eine App-Instanz macht eine Teilmenge der aktuellen globalen APIs verfügbar. Damit werden alle APIs (Komponente, Mixin, Anweisung, Verwendung usw.), die Vue
von Vue 2.x mutieren, jetzt in einzelne App-Instanzen verschoben, und jetzt kann jede Instanz Ihrer Vue-App Funktionen haben, die einzigartig sind sie, ohne andere vorhandene Apps zu beeinträchtigen.
Jetzt kann der obige Code umgeschrieben werden als;
const app1 = createApp({}) const app2 = createApp({}) app1.directive('focus', { inserted: el => el.focus() }) app2.mixin({ /* ... */ })
Es ist jedoch möglich, Funktionalitäten zu erstellen, die Sie für alle Ihre Apps freigeben möchten, und dies kann mithilfe einer Factory-Funktion erfolgen.
Ereignis-API
Eine der häufigsten Methoden, die Entwickler zum Übergeben von Daten zwischen Komponenten gewählt haben, die keine Eltern-Kind-Beziehung haben, außer der Verwendung des Vuex Store, ist die Verwendung von Event Bus. Einer der Gründe, warum diese Methode üblich ist, ist, wie einfach es ist, damit anzufangen;
# eventBus.js const eventBus = new Vue() export default eventBus;
Danach wäre der nächste Schritt, diese Datei in main.js
zu importieren, um sie global in unserer App verfügbar zu machen oder sie in Dateien zu importieren, die Sie benötigen.
# main.js import eventBus from 'eventBus' Vue.prototype.$eventBus = eventBus
Jetzt können Sie Ereignisse ausgeben und auf solche ausgegebenen Ereignisse lauschen;
this.$eventBus.$on('say-hello', alertMe) this.$eventBus.$emit('pass-message', 'Event Bus says Hi')
Es gibt eine Menge Vue-Codebasis, die mit Code wie diesem gefüllt ist. Mit Vue 3 wäre dies jedoch unmöglich, da $on
, $off
und $once
alle entfernt wurden, aber $emit
immer noch verfügbar ist, da es erforderlich ist, dass untergeordnete Komponenten Ereignisse an ihre übergeordneten Komponenten ausgeben. Eine Alternative dazu wäre die Verwendung provide / inject
oder einer der empfohlenen Bibliotheken von Drittanbietern.
Fazit
In diesem Artikel haben wir behandelt, wie Sie mithilfe des provide / inject
Daten von einer übergeordneten Komponente an eine tief verschachtelte untergeordnete Komponente weitergeben können. Wir haben uns auch angesehen, wie wir Komponenten von einem Punkt in unserer App zu einem anderen neu positionieren und übertragen können. Eine andere Sache, die wir uns angesehen haben, ist die Multi-Root-Node-Komponente und wie wir sicherstellen können, dass wir Attribute verteilen, damit sie richtig funktionieren. Schließlich haben wir auch die Änderungen an der Events-API und der globalen API behandelt.
Weitere Ressourcen
- „JavaScript Factory-Funktionen mit ES6+“, Eric Elliott, Medium
- „Verwenden von Event Bus zum Teilen von Requisiten zwischen Vue-Komponenten“, Kingsley Silas, CSS-Tricks
- Using Multiple Teleports on the same target, Vue.js Docs
- Nicht-Prop-Attribute, Vue.js-Dokumentation
- Arbeiten mit Reaktivität, Vue.js Docs
-
teleport
, Vue.js Docs - Fragmente, Vue.js-Dokumente
- 2.x-Syntax, Vue.js-Dokumentation