Vue.js est probablement l'un des plus beaux frameworks JavaScript. Il possède une API intuitive, il est rapide, flexible et facile à utiliser. Cependant, la flexibilité de Vue.js présente certains dangers. Certains développeurs travaillant avec ce cadre sont sujets à de petites omissions. Cela peut nuire aux performances des applications ou, à long terme, à la capacité de les prendre en charge.

L'auteur du matériel, dont nous publions la traduction aujourd'hui, propose d'analyser certaines erreurs courantes commises par ceux qui développent des applications sur Vue.js.
Effets secondaires à l'intérieur des propriétés calculées
Les propriétés calculées sont un mécanisme Vue.js très pratique qui vous permet d'organiser le travail avec des fragments d'état qui dépendent d'autres fragments d'état. Les propriétés calculées ne doivent être utilisées que pour afficher les données stockées dans un état qui dépend d'autres données de l'état. S'il s'avère que vous appelez des méthodes à l'intérieur des propriétés calculées ou que vous écrivez des valeurs dans d'autres variables d'état, cela peut signifier que vous faites quelque chose de mal. Prenons un exemple.
export default { data() { return { array: [1, 2, 3] }; }, computed: { reversedArray() { return this.array.reverse();
Si nous essayons d'imprimer un
array
et un
array
reversedArray
, nous remarquerons que les deux tableaux contiennent les mêmes valeurs.
: [ 3, 2, 1 ] : [ 3, 2, 1 ]
Cela est dû au fait que la propriété
.reverse()
calculée modifie la propriété de
array
origine en appelant sa méthode
.reverse()
. Il s'agit d'un exemple assez simple qui montre un comportement système inattendu. Jetez un oeil à un autre exemple.
Supposons que nous ayons un composant qui affiche des informations détaillées sur le prix des biens ou des services inclus dans une certaine commande.
export default { props: { order: { type: Object, default: () => ({}) } }, computed:{ grandTotal() { let total = (this.order.total + this.order.tax) * (1 - this.order.discount); this.$emit('total-change', total) return total.toFixed(2); } } }
Ici, nous avons créé une propriété calculée qui affiche le coût total de la commande, y compris les taxes et les remises. Puisque nous savons que la valeur totale de la commande change ici, nous pouvons essayer de déclencher un événement qui notifie le composant parent d'une modification
grandTotal
.
<price-details :order="order" @total-change="totalChange"> </price-details> export default { // methods: { totalChange(grandTotal) { if (this.isSpecialCustomer) { this.order = { ...this.order, discount: this.order.discount + 0.1 }; } } } };
Imaginez maintenant que parfois, bien que très rarement, des situations surviennent dans lesquelles nous travaillons avec des clients spéciaux. Nous accordons à ces clients une remise supplémentaire de 10%. Nous pouvons essayer de modifier l'objet de la
order
et augmenter la taille de la remise en ajoutant
0.1
à sa propriété de
discount
.
Cependant, cela conduira à une mauvaise erreur.
Message d'erreurCalcul de la valeur de la commande incorrect pour un client spécialDans une situation similaire, la situation suivante se produit: la propriété calculée est constamment, dans une boucle infinie, «recomptée». Nous modifions la remise, la propriété calculée réagit à cela, recalcule le coût total de la commande et génère un événement. Lors du traitement de cet événement, la remise augmente à nouveau, ce qui provoque un recalcul de la propriété calculée, etc. - à l'infini.
Il peut vous sembler qu'une telle erreur ne peut pas être commise dans une application réelle. Mais est-ce vraiment le cas? Notre script (si quelque chose comme cela se produit dans cette application) sera très difficile à déboguer. Une telle erreur sera extrêmement difficile à suivre. Le fait est que pour que cette erreur se produise, il est nécessaire que la commande soit effectuée par un acheteur spécial, et une telle commande peut avoir 1000 commandes régulières.
Modification des propriétés imbriquées
Parfois, un développeur peut être tenté de modifier quelque chose dans une propriété à partir d'
props
, qui est un objet ou un tableau. Un tel désir peut être dicté par le fait qu'il est très «simple» de le faire. Mais ça vaut le coup? Prenons un exemple.
<template> <div class="hello"> <div>Name: {{product.name}}</div> <div>Price: {{product.price}}</div> <div>Stock: {{product.stock}}</div> <button @click="addToCart" :disabled="product.stock <= 0">Add to card</button> </div> </template> export default { name: "HelloWorld", props: { product: { type: Object, default: () => ({}) } }, methods: { addToCart() { if (this.product.stock > 0) { this.$emit("add-to-cart"); this.product.stock--; } } } };
Nous avons ici le composant
Product.vue
, qui affiche le nom du produit, sa valeur et la quantité de marchandises que nous avons. Le composant affiche également un bouton qui permet à l'acheteur de placer les marchandises dans le panier. Il peut sembler qu'il sera très facile et pratique de diminuer la valeur de la propriété
product.stock
après avoir cliqué sur le bouton. Pour ce faire, c'est vraiment simple. Mais si vous faites cela, vous pouvez rencontrer plusieurs problèmes:
- Nous effectuons un changement (mutation) de la propriété et ne signalons rien à l'entité mère.
- Cela peut conduire à un comportement inattendu du système ou, pire encore, à l'apparition d'erreurs étranges.
- Nous introduisons une certaine logique dans le composant du
product
, qui ne devrait probablement pas y être présent.
Imaginez une situation hypothétique dans laquelle un autre développeur rencontre d'abord notre code et voit le composant parent.
<template> <Product :product="product" @add-to-cart="addProductToCart(product)"></Product> </template> import Product from "./components/Product"; export default { name: "App", components: { Product }, data() { return { product: { name: "Laptop", price: 1250, stock: 2 } }; }, methods: { addProductToCart(product) { if (product.stock > 0) { product.stock--; } } } };
La pensée de ce développeur peut être la suivante: "Apparemment, je dois réduire
addProductToCart
dans la méthode
addProductToCart
" Mais si cela est fait, nous rencontrerons une petite erreur. Si maintenant appuyez sur le bouton, la quantité de marchandises sera réduite non pas de 1, mais de 2.
Imaginez qu'il s'agit d'un cas particulier lorsqu'un tel contrôle est effectué uniquement pour des biens rares ou en rapport avec la disponibilité d'une remise spéciale. Si ce code entre en production, tout peut aboutir au fait que nos clients vont, au lieu d'une copie du produit, en acheter deux.
Si cet exemple vous semble peu convaincant, imaginez un autre scénario. Que ce soit le formulaire que l'utilisateur remplisse. Nous transmettons l'essence de l'
user
au formulaire en tant que propriété et allons modifier le nom et l'adresse e-mail de l'utilisateur. Le code ci-dessous peut sembler être «correct».
Il est facile de commencer avec l'
user
à l'aide de la directive
v-model
. Vue.js le permet. Pourquoi ne pas faire ça? Pensez-y:
- Que se passe-t-il si vous devez ajouter un bouton Annuler au formulaire, en cliquant sur ce qui annule les modifications apportées?
- Et si un appel serveur échoue? Comment annuler
user
modifications d'objet user
? - Voulons-nous vraiment afficher le nom et l'adresse e-mail modifiés dans le composant parent avant d'enregistrer les modifications correspondantes?
Un moyen simple de «résoudre» le problème peut être de cloner l'objet
user
avant de l'envoyer en tant que propriété:
<user-form :user="{...user}">
Bien que cela puisse fonctionner, nous contournons uniquement le problème, mais ne le résolvons pas. Notre composant
UserForm
doit avoir son propre état local. Voici ce que nous pouvons faire.
<template> <div> <input placeholder="Email" type="email" v-model="form.email"/> <input placeholder="Name" v-model="form.name"/> <button @click="onSave">Save</button> <button @click="onCancel">Save</button> </div> </template> export default { props: { user: { type: Object, default: () => ({}) } }, data() { return { form: {} } }, methods: { onSave() { this.$emit('submit', this.form) }, onCancel() { this.form = {...this.user} this.$emit('cancel') } } watch: { user: { immediate: true, handler: function(userFromProps){ if(userFromProps){ this.form = { ...this.form, ...userFromProps } } } } } }
Bien que ce code semble définitivement assez compliqué, il est meilleur que la version précédente. Il vous permet de vous débarrasser des problèmes ci-dessus. Nous attendons (
watch
) les modifications apportées à la propriété
user
et les copions dans les données du
form
interne. Par conséquent, le formulaire a maintenant son propre état et nous obtenons les fonctionnalités suivantes:
- Vous pouvez annuler les modifications en réaffectant le formulaire:
this.form = {...this.user}
. - Nous avons un état isolé pour le formulaire.
- Nos actions n'affectent pas le composant parent au cas où nous n'en aurions pas besoin.
- Nous contrôlons ce qui se passe lorsque nous essayons d'enregistrer les modifications.
Accès direct aux composants parents
Si un composant fait référence à un autre composant et exécute certaines actions sur celui-ci, cela peut entraîner des contradictions et des erreurs, cela peut entraîner un comportement étrange de l'application et l'apparition de composants associés.
Prenons un exemple très simple - un composant qui implémente un menu déroulant. Imaginez que nous ayons un composant
dropdown
(parent) et un composant de
dropdown-menu
(enfant). Lorsque l'utilisateur clique sur un certain élément de menu, nous devons fermer le
dropdown-menu
. Le masquage et l'affichage de ce composant sont effectués par le composant parent de la
dropdown
. Jetez un oeil à un exemple.
Faites attention à la méthode
selectOption
. Bien que cela se produise très rarement, quelqu'un peut vouloir contacter directement
$parent
. Ce désir s'explique par le fait qu'il est très simple à faire.
À première vue, il peut sembler que ce code fonctionne correctement. Mais ici, vous pouvez voir quelques problèmes:
- Que faire si nous modifions la propriété
showMenu
ou selectedOption
? Le menu déroulant ne pourra pas se fermer et aucun de ses éléments ne sera sélectionné. - Et si vous avez besoin d'animer le
dropdown-menu
utilisant une sorte de transition?
Ce code, encore une fois, en raison d'un changement dans
$parent
, ne fonctionnera pas. Le composant
dropdown
n'est plus le parent du
dropdown-menu
. Le parent du
dropdown-menu
est maintenant le composant de
transition
.
Les propriétés sont transmises dans la hiérarchie des composants, les événements sont transmis. Ces mots contiennent le sens de la bonne approche pour résoudre notre problème. Voici un exemple modifié pour les événements.
Maintenant, grâce à l'utilisation d'événements, le composant enfant n'est plus lié au composant parent. Nous pouvons modifier librement les propriétés avec des données dans le composant parent et utiliser des transitions animées. Cependant, nous ne pouvons pas penser à la façon dont notre code peut affecter le composant parent. Nous informons simplement ce composant de ce qui s'est passé. Dans ce cas, le composant
dropdown
lui-même prend des décisions sur la façon de gérer le choix de l'utilisateur d'un élément de menu et l'opération de fermeture du menu.
Résumé
Le code le plus court n'est pas toujours le plus réussi. Les techniques de développement impliquant des résultats «simples et rapides» sont souvent imparfaites. Afin d'utiliser correctement n'importe quel langage de programmation, bibliothèque ou framework, vous avez besoin de patience et de temps. Cela est vrai pour Vue.js.
Chers lecteurs! Avez-vous rencontré des problèmes dans la pratique, tels que ceux abordés dans cet article?
