Quoi de neuf dans Vue 3 ?
Publié: 2022-03-10Avec la sortie de Vue 3, les développeurs doivent effectuer la mise à niveau à partir de Vue 2 car il est livré avec une poignée de nouvelles fonctionnalités qui sont très utiles pour créer des composants faciles à lire et maintenables et des moyens améliorés de structurer notre application dans Vue. Nous allons examiner certaines de ces fonctionnalités dans cet article.
À la fin de ce didacticiel, les lecteurs comprendront ;
- Connaître
provide / inject
et comment l'utiliser. - Avoir une compréhension de base de Teleport et comment l'utiliser.
- Connaître les fragments et savoir comment les utiliser.
- Connaître les modifications apportées à l'API Global Vue.
- Connaître les modifications apportées à l'API Events.
Cet article est destiné à ceux qui ont une bonne compréhension de Vue 2.x. Vous pouvez trouver tout le code utilisé dans cet exemple dans GitHub.
provide / inject
Dans Vue 2.x, nous avions props
qui facilitaient le passage de données (chaîne, tableaux, objets, etc.) d'un composant parent directement à son composant enfant. Mais pendant le développement, nous avons souvent trouvé des cas où nous devions transmettre des données du composant parent à un composant profondément imbriqué, ce qui était plus difficile à faire avec props
. Cela a entraîné l'utilisation de Vuex Store, Event Hub et parfois la transmission de données via les composants profondément imbriqués. Regardons une application simple ;
Il est important de noter que Vue 2.2.0 est également fourni avec provide / inject
qu'il n'est pas recommandé d'utiliser dans le code d'application générique.
# 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>
Ici, nous avons une page de destination avec une liste déroulante contenant une liste de couleurs et nous transmettons la color
sélectionnée à childComponent.vue
en tant que prop. Ce composant enfant a également un accessoire msg
qui accepte un texte à afficher dans la section modèle. Enfin, ce composant a un composant enfant ( colorComponent.vue
) qui accepte un accessoire de color
du composant parent qui est utilisé pour déterminer la classe du texte dans ce composant. Ceci est un exemple de transmission de données à travers tous les composants.
Mais avec Vue 3, nous pouvons le faire de manière plus propre et plus courte en utilisant la nouvelle paire Provide et inject. Comme son nom l'indique, nous utilisons provide
en tant que fonction ou objet pour rendre les données disponibles d'un composant parent à l'un de ses composants imbriqués, quelle que soit la profondeur d'imbrication d'un tel composant. Nous utilisons le formulaire d'objet lors de la transmission de valeurs codées en dur pour provide
comme ceci ;
# 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>
Mais pour les instances où vous devez transmettre une propriété d'instance de composant à provide
, nous utilisons le mode fonction afin que cela soit possible ;
# 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>
Comme nous n'avons pas besoin des accessoires de color
dans childComponent.vue
et colorComponent.vue
, nous nous en débarrassons. L'avantage d'utiliser provide
est que le composant parent n'a pas besoin de savoir quel composant a besoin de la propriété qu'il fournit.
Pour l'utiliser dans le composant qui en a besoin dans ce cas, colorComponent.vue
, nous le faisons ;
# 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>
Ici, nous utilisons inject
qui prend un tableau des variables requises dont le composant a besoin. Dans ce cas, nous n'avons besoin que de la propriété de color
, nous ne la transmettons donc qu'à cela. Après cela, nous pouvons utiliser la color
de la même manière que nous l'utilisons lorsque nous utilisons des accessoires.
Nous pouvons remarquer que si nous essayons de sélectionner une nouvelle couleur à l'aide de la liste déroulante, la couleur ne se met pas à jour dans colorComponent.vue
et c'est parce que, par défaut, les propriétés fournies provide
sont pas réactives. Pour résoudre ce problème, nous utilisons une méthode 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>
Ici, nous importons le computed
et transmettons notre selectedColor
afin qu'il puisse être réactif et mis à jour lorsque l'utilisateur sélectionne une couleur différente. Lorsque vous passez une variable à la méthode calculée, elle renvoie un objet qui a une value
. Cette propriété contient la valeur de votre variable, donc pour cet exemple, nous devrions mettre à jour colorComponent.vue
pour qu'il ressemble à ceci ;
# 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>
Ici, nous changeons color
en color.value
pour représenter le changement après avoir rendu color
réactive à l'aide de la méthode computed
. À ce stade, la class
du texte dans ce composant changerait toujours chaque fois que selectedColor
changerait dans le composant parent.
Téléportation
Il y a des cas où nous créons des composants et les plaçons dans une partie de notre application en raison de la logique utilisée par l'application, mais sont destinés à être affichés dans une autre partie de notre application. Un exemple courant de ceci serait un modal ou un popup destiné à afficher et à couvrir tout l'écran. Bien que nous puissions créer une solution de contournement pour cela en utilisant la propriété position
de CSS sur de tels éléments, avec Vue 3, nous pouvons également utiliser Teleport.
La téléportation nous permet de retirer un composant de sa position d'origine dans un document, à partir du conteneur #app
par défaut dans lequel les applications Vue sont enveloppées et de le déplacer vers n'importe quel élément existant sur la page qu'il utilise. Un bon exemple serait d'utiliser Teleport pour déplacer un composant d'en-tête de l'intérieur de la div #app
vers un header
. Il est important de noter que vous ne pouvez vous téléporter que vers des éléments qui existent en dehors de Vue DOM.

Le composant Teleport accepte deux accessoires qui déterminent le comportement de ce composant et ils le sont ;

-
to
Ce prop accepte soit un nom de classe, un identifiant, un élément ou un attribut data-*. Nous pouvons également rendre cette valeur dynamique en passant un:to
prop au lieu deto
et changer dynamiquement l'élément Teleport. -
:disabled
Cet accessoire accepte unBoolean
et peut être utilisé pour basculer la fonction de téléportation sur un élément ou un composant. Cela peut être utile pour modifier dynamiquement la position d'un élément.
Un exemple idéal d'utilisation de Teleport ressemble à ceci ;
# 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>
Dans le fichier index.html
par défaut de votre application Vue, nous ajoutons un élément d' header
car nous souhaitons téléporter notre composant d'en-tête à ce point de notre application. Nous avons également ajouté une classe à cet élément pour le style et pour faciliter le référencement dans notre composant Teleport.
# 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>
Ici, nous créons le composant d'en-tête et ajoutons un logo avec un lien vers la page d'accueil de notre application. Nous ajoutons également le composant Teleport et donnons to
prop une valeur d' header
car nous voulons que ce composant s'affiche à l'intérieur de cet élément. Enfin, nous importons ce composant dans notre application ;
# App.vue <template> <router-view /> <app-header></app-header> </template> <script> import appHeader from "@/components/Header.vue"; export default { components: { appHeader, }, }; </script>
Dans ce fichier, nous importons le composant d'en-tête et le plaçons dans le modèle afin qu'il soit visible dans notre application.
Maintenant, si nous inspectons l'élément de notre application, nous remarquerons que notre composant d'en-tête se trouve à l'intérieur de l'élément d'en- header
;

fragments
Avec Vue 2.x, il était impossible d'avoir plusieurs éléments racine dans le template
de votre fichier et comme solution de contournement, les développeurs ont commencé à envelopper tous les éléments dans un élément parent. Bien que cela ne semble pas être un problème sérieux, il existe des cas où les développeurs souhaitent rendre un composant sans conteneur entourant ces éléments, mais doivent s'en contenter.
Avec Vue 3, une nouvelle fonctionnalité appelée Fragments a été introduite et cette fonctionnalité permet aux développeurs d'avoir plusieurs éléments dans leur fichier de modèle racine. Ainsi, avec Vue 2.x, voici à quoi ressemblerait un composant de conteneur de champ de saisie ;
# 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>
Ici, nous avons un composant d'élément de formulaire simple qui accepte deux props, label
et type
, et la section template de ce composant est enveloppée dans un div. Ce n'est pas nécessairement un problème, mais si vous voulez que l'étiquette et le champ de saisie soient directement à l'intérieur de votre élément de form
. Avec Vue 3, les développeurs peuvent facilement réécrire ce composant pour qu'il ressemble à ceci ;
# inputComponent.vue <template class="testingss"> <label :for="label">{{ label }}</label> <input :type="type" : :name="label" /> </template>
Avec un seul nœud racine, les attributs sont toujours attribués au nœud racine et ils sont également appelés attributs non prop . Ce sont des événements ou des attributs passés à un composant qui n'ont pas de propriétés correspondantes définies dans props
ou les emits
. Des exemples de tels attributs sont class
et id
. Il est toutefois nécessaire de définir explicitement à quel élément d'un composant de nœud multiracine doit être attribué.
Voici ce que cela signifie en utilisant le inputComponent.vue
ci-dessus;
- Lors de l'ajout d'une
class
à ce composant dans le composant parent, il faut spécifier à quel composant cetteclass
serait attribuée sinon l'attribut n'a aucun effet.
<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>
Lorsque vous faites quelque chose comme ça sans définir où les attributs doivent être attribués, vous obtenez cet avertissement dans votre console ;

Et la border
n'a aucun effet sur le composant ;

- Pour résoudre ce problème, ajoutez un
v-bind="$attrs"
sur l'élément auquel vous souhaitez que ces attributs soient distribués ;
<template> <label :for="label" v-bind="$attrs">{{ label }}</label> <input :type="type" : :name="label" /> </template>
Ici, nous disons à Vue que nous voulons que les attributs soient distribués à l'élément label
, ce qui signifie que nous voulons que la awesome__class
lui soit appliquée. Maintenant, si nous inspectons notre élément dans le navigateur, nous verrons que la classe a maintenant été ajoutée à l' label
et donc une bordure est maintenant autour de l'étiquette.

API globale
Il n'était pas rare de voir Vue.component
ou Vue.use
dans le fichier main.js
d'une application Vue. Ces types de méthodes sont connus pour être des API globales et il en existe un certain nombre dans Vue 2.x. L'un des défis de cette méthode est qu'il est impossible d'isoler certaines fonctionnalités d'une instance de votre application (si vous avez plusieurs instances dans votre application) sans que cela n'affecte d'autres applications car elles sont toutes montées sur Vue. C'est ce que je veux dire;
Vue.directive('focus', { inserted: el => el.focus() }) Vue.mixin({ /* ... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })
Pour le code ci-dessus, il est impossible d'affirmer que la directive Vue soit associée à app1
et le Mixin à app2
mais à la place, ils sont tous deux disponibles dans les deux applications.
Vue 3 est livré avec une nouvelle API globale pour tenter de résoudre ce type de problème avec l'introduction de createApp
. Cette méthode renvoie une nouvelle instance d'une application Vue. Une instance d'application expose un sous-ensemble des API globales actuelles. Avec cela, toutes les API (composant, mixin, directive, utilisation, etc.) qui transforment Vue
de Vue 2.x vont maintenant être déplacées vers des instances d'application individuelles et maintenant, chaque instance de votre application Vue peut avoir des fonctionnalités uniques à sans affecter les autres applications existantes.
Maintenant, le code ci-dessus peut être réécrit comme;
const app1 = createApp({}) const app2 = createApp({}) app1.directive('focus', { inserted: el => el.focus() }) app2.mixin({ /* ... */ })
Il est cependant possible de créer des fonctionnalités que vous souhaitez partager entre toutes vos applications et cela peut être fait en utilisant une fonction d'usine.
API d'événements
L'une des méthodes les plus couramment adoptées par les développeurs pour transmettre des données entre des composants qui n'ont pas de relation parent à enfant autre que l'utilisation de Vuex Store est l'utilisation du bus d'événements. L'une des raisons pour lesquelles cette méthode est courante est qu'elle est facile à utiliser.
# eventBus.js const eventBus = new Vue() export default eventBus;
Après cela, la prochaine chose serait d'importer ce fichier dans main.js
pour le rendre globalement disponible dans notre application ou de l'importer dans les fichiers dont vous avez besoin ;
# main.js import eventBus from 'eventBus' Vue.prototype.$eventBus = eventBus
Maintenant, vous pouvez émettre des événements et écouter les événements émis comme celui-ci ;
this.$eventBus.$on('say-hello', alertMe) this.$eventBus.$emit('pass-message', 'Event Bus says Hi')
Il y a beaucoup de base de code Vue qui est remplie de code comme celui-ci. Cependant, avec Vue 3, il serait impossible de le faire car $on
, $off
et $once
ont tous été supprimés mais $emit
est toujours disponible car il est nécessaire que les composants enfants émettent des événements vers leurs composants parents. Une alternative à cela serait d'utiliser provide / inject
ou l'une des bibliothèques tierces recommandées.
Conclusion
Dans cet article, nous avons expliqué comment vous pouvez transmettre des données d'un composant parent à un composant enfant profondément imbriqué à l'aide de la paire provide / inject
. Nous avons également examiné comment nous pouvons repositionner et transférer des composants d'un point à un autre de notre application. Une autre chose que nous avons examinée est le composant de nœud multi-racine et comment nous assurer que nous distribuons les attributs afin qu'ils fonctionnent correctement. Enfin, nous avons également couvert les modifications apportées à l'API des événements et à l'API globale.
Autres ressources
- "Fonctions d'usine JavaScript avec ES6 +", Eric Elliott, moyen
- "Utilisation du bus d'événements pour partager des accessoires entre les composants Vue", Kingsley Silas, CSS-Tricks
- Utilisation de plusieurs téléports sur la même cible, Vue.js Docs
- Attributs non prop, Docs Vue.js
- Travailler avec la réactivité, Vue.js Docs
-
teleport
, Vue.js Docs - Fragments, documentation Vue.js
- Syntaxe 2.x, documentation Vue.js