Vue.js: 3 Anti-Patterns

Vue.js ist wahrscheinlich eines der schönsten JavaScript-Frameworks. Es verfügt über eine intuitive API, ist schnell, flexibel und einfach zu bedienen. Die Flexibilität von Vue.js birgt jedoch gewisse Gefahren. Einige Entwickler, die mit diesem Framework arbeiten, neigen zu kleinen Versehen. Dies kann sich nachteilig auf die Anwendungsleistung oder auf lange Sicht auf die Fähigkeit auswirken, diese zu unterstützen.



Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, bietet an, einige häufige Fehler zu analysieren, die von denjenigen gemacht wurden, die Anwendungen auf Vue.js entwickeln.

Nebenwirkungen innerhalb berechneter Eigenschaften


Berechnete Eigenschaften sind ein sehr praktischer Vue.js-Mechanismus, mit dem Sie die Arbeit mit Statusfragmenten organisieren können, die von anderen Statusfragmenten abhängen. Berechnete Eigenschaften sollten nur verwendet werden, um Daten anzuzeigen, die in einem Zustand gespeichert sind, der von anderen Daten aus dem Zustand abhängt. Wenn sich herausstellt, dass Sie einige Methoden innerhalb der berechneten Eigenschaften aufrufen oder einige Werte in andere Statusvariablen schreiben, kann dies bedeuten, dass Sie etwas falsch machen. Betrachten Sie ein Beispiel.

export default {   data() {     return {       array: [1, 2, 3]     };   },   computed: {     reversedArray() {       return this.array.reverse(); //   -         }   } }; 

Wenn wir versuchen, array und reversedArray zu drucken, werden wir feststellen, dass beide Arrays dieselben Werte enthalten.

  : [ 3, 2, 1 ]  : [ 3, 2, 1 ] 

Dies liegt daran, dass die berechnete Eigenschaft reversedArray die ursprüngliche array Eigenschaft durch Aufrufen der Methode .reverse() . Dies ist ein ziemlich einfaches Beispiel, das unerwartetes Systemverhalten demonstriert. Schauen Sie sich ein anderes Beispiel an.

Angenommen, wir haben eine Komponente, die detaillierte Informationen zum Preis von Waren oder Dienstleistungen anzeigt, die in einer bestimmten Bestellung enthalten sind.

 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);    }  } } 

Hier haben wir eine berechnete Eigenschaft erstellt, die die Gesamtkosten der Bestellung einschließlich Steuern und Rabatten anzeigt. Da wir wissen, dass sich der Gesamtauftragswert hier ändert, können wir versuchen, ein Ereignis grandTotal , das die übergeordnete Komponente über eine Änderung von grandTotal benachrichtigt.

 <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        };      }    }  } }; 

Stellen Sie sich nun vor, dass manchmal, wenn auch sehr selten, Situationen auftreten, in denen wir mit speziellen Kunden zusammenarbeiten. Wir gewähren diesen Kunden einen zusätzlichen Rabatt von 10%. Wir können versuchen, das Bestellobjekt zu ändern und die Rabattgröße zu erhöhen, indem wir der Rabatteigenschaft 0.1 hinzufügen.

Dies führt jedoch zu einem schlimmen Fehler.


Fehlermeldung


Falsche Bestellwertberechnung für einen speziellen Kunden

In einer ähnlichen Situation tritt Folgendes auf: Die berechnete Eigenschaft wird in einer Endlosschleife ständig „nachgezählt“. Wir ändern den Rabatt, die berechnete Immobilie reagiert darauf, berechnet die Gesamtkosten der Bestellung neu und generiert ein Ereignis. Bei der Verarbeitung dieses Ereignisses erhöht sich der Rabatt erneut, dies führt zu einer Neuberechnung der berechneten Eigenschaft usw. bis ins Unendliche.

Es mag Ihnen scheinen, dass ein solcher Fehler in einer realen Anwendung nicht gemacht werden kann. Aber ist es wirklich so? Unser Skript (wenn so etwas in dieser Anwendung passiert) ist sehr schwer zu debuggen. Ein solcher Fehler wird äußerst schwer zu verfolgen sein. Tatsache ist, dass für diesen Fehler die Bestellung von einem speziellen Käufer ausgeführt werden muss und eine solche Bestellung 1000 reguläre Bestellungen haben kann.

Verschachtelte Eigenschaften ändern


Manchmal ist ein Entwickler versucht, etwas in einer Eigenschaft aus props zu bearbeiten, bei denen es sich um ein Objekt oder ein Array handelt. Ein solcher Wunsch kann durch die Tatsache diktiert werden, dass es sehr „einfach“ ist, dies zu tun. Aber ist es das wert? Betrachten Sie ein Beispiel.

 <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--;      }    }  } }; 

Hier haben wir die Product.vue Komponente, die den Namen des Produkts, seinen Wert und die Menge der Waren anzeigt, die wir haben. Die Komponente zeigt auch eine Schaltfläche an, mit der der Käufer die Waren in den Warenkorb legen kann. Es scheint sehr einfach und bequem zu sein, den Wert der Eigenschaft product.stock nach dem Klicken auf die Schaltfläche zu verringern. Dies zu tun ist wirklich einfach. Wenn Sie genau das tun, können jedoch mehrere Probleme auftreten:

  • Wir führen eine Änderung (Mutation) der Eigenschaft durch und melden nichts an die übergeordnete Entität.
  • Dies kann zu unerwartetem Systemverhalten oder, noch schlimmer, zum Auftreten seltsamer Fehler führen.
  • Wir führen eine Logik in die product , die wahrscheinlich nicht darin vorhanden sein sollte.

Stellen Sie sich eine hypothetische Situation vor, in der ein anderer Entwickler zuerst auf unseren Code stößt und die übergeordnete Komponente sieht.

 <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--;      }    }  } }; 

Dieser Entwickler könnte wie folgt denken: "Anscheinend muss ich den product.stock in der addProductToCart Methode reduzieren addProductToCart " Aber wenn dies getan wird, werden wir auf einen kleinen Fehler stoßen. Wenn Sie jetzt die Taste drücken, wird die Warenmenge nicht um 1, sondern um 2 reduziert.

Stellen Sie sich vor, dies ist ein Sonderfall, wenn eine solche Prüfung nur für seltene Waren oder im Zusammenhang mit der Verfügbarkeit eines Sonderrabattes durchgeführt wird. Wenn dieser Code in Produktion geht, kann alles dazu führen, dass unsere Kunden anstelle von 1 Kopie des Produkts 2 Kopien kaufen.

Wenn Ihnen dieses Beispiel nicht überzeugend erschien, stellen Sie sich ein anderes Szenario vor. Es sei das Formular, das der Benutzer ausfüllt. Wir übergeben die Essenz des user als Eigenschaft an das Formular und werden den Namen und die E-Mail-Adresse des Benutzers bearbeiten. Der unten gezeigte Code scheint "korrekt" zu sein.

 //   <template>  <div>    <span> Email {{user.email}}</span>    <span> Name {{user.name}}</span>    <user-form :user="user" @submit="updateUser"/>  </div> </template> import UserForm from "./UserForm" export default {  components: {UserForm},  data() {   return {     user: {      email: 'loreipsum@email.com',      name: 'Lorem Ipsum'     }   }  },  methods: {    updateUser() {     //            }  } } //   UserForm.vue <template>  <div>   <input placeholder="Email" type="email" v-model="user.email"/>   <input placeholder="Name" v-model="user.name"/>   <button @click="$emit('submit')">Save</button>  </div> </template> export default {  props: {    user: {     type: Object,     default: () => ({})    }  } } 

Mit der v-model Direktive können Sie ganz einfach mit dem user . Vue.js erlaubt dies. Warum nicht einfach das tun? Denken Sie darüber nach:

  • Was ist, wenn Sie dem Formular eine Schaltfläche Abbrechen hinzufügen müssen, indem Sie darauf klicken, welche Schaltfläche die vorgenommenen Änderungen abbricht?
  • Was ist, wenn ein Serveraufruf fehlschlägt? Wie mache ich user rückgängig?
  • Möchten wir den geänderten Namen und die geänderte E-Mail-Adresse wirklich in der übergeordneten Komponente anzeigen, bevor wir die entsprechenden Änderungen speichern?

Eine einfache Möglichkeit, das Problem zu beheben, besteht darin, das user zu klonen, bevor es als Eigenschaft gesendet wird:

 <user-form :user="{...user}"> 

Obwohl dies funktionieren mag, umgehen wir das Problem nur, lösen es aber nicht. Unsere UserForm Komponente muss einen eigenen lokalen Status haben. Folgendes können wir tun.

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

Obwohl dieser Code definitiv ziemlich kompliziert erscheint, ist er besser als die vorherige Version. Es ermöglicht Ihnen, die oben genannten Probleme zu beseitigen. Wir erwarten ( watch ) Änderungen an der user und kopieren sie in die internen form . Infolgedessen hat das Formular jetzt einen eigenen Status, und wir erhalten die folgenden Funktionen:

  • Sie können die Änderungen rückgängig machen, indem Sie das Formular neu this.form = {...this.user} : this.form = {...this.user} .
  • Wir haben einen isolierten Zustand für die Form.
  • Unsere Aktionen wirken sich nicht auf die übergeordnete Komponente aus, falls wir sie nicht benötigen.
  • Wir kontrollieren, was passiert, wenn wir versuchen, Änderungen zu speichern.

Direkter Zugriff auf übergeordnete Komponenten


Wenn eine Komponente auf eine andere Komponente verweist und einige Aktionen darauf ausführt, kann dies zu Widersprüchen und Fehlern führen. Dies kann zu einem merkwürdigen Verhalten der Anwendung und zum Auftreten verwandter Komponenten führen.

Stellen Sie sich ein sehr einfaches Beispiel vor - eine Komponente, die ein Dropdown-Menü implementiert. Stellen Sie sich vor, wir haben eine dropdown Komponente (übergeordnet) und eine dropdown-menu dropdown ( dropdown-menu ). Wenn der Benutzer auf einen bestimmten Menüpunkt klickt, müssen wir das dropdown-menu . Das Ausblenden und dropdown dieser Komponente erfolgt durch die übergeordnete dropdown Komponente. Schauen Sie sich ein Beispiel an.

 // Dropdown.vue ( ) <template>  <div>    <button @click="showMenu = !showMenu">Click me</button>    <dropdown-menu v-if="showMenu" :items="items"></dropdown-menu>  </div> <template> export default {  props: {   items: Array  },  data() {   return {     selectedOption: null,     showMenu: false   }  } } // DropdownMenu.vue ( ) <template>  <ul>    <li v-for="item in items" @click="selectOption(item)">{{item.name}}</li>  </ul> <template> export default {  props: {   items: Array  },  methods: {    selectOption(item) {     this.$parent.selectedOption = item     this.$parent.showMenu = false    }  } } 

selectOption auf die selectOption Methode. Obwohl dies sehr selten vorkommt, möchte jemand möglicherweise $parent direkt kontaktieren. Dieser Wunsch kann durch die Tatsache erklärt werden, dass es sehr einfach ist, dies zu tun.

Auf den ersten Blick scheint es, dass ein solcher Code korrekt funktioniert. Aber hier sehen Sie ein paar Probleme:

  • Was ist, wenn wir die Eigenschaft showMenu oder selectedOption ändern? Das Dropdown-Menü kann nicht geschlossen werden und keines seiner Elemente wird ausgewählt.
  • Was ist, wenn Sie das dropdown-menu mithilfe eines Übergangs animieren dropdown-menu ?

 // Dropdown.vue ( ) <template>  <div>    <button @click="showMenu = !showMenu">Click me</button>    <transition name="fade">      <dropdown-menu v-if="showMenu" :items="items"></dropdown-menu>    </dropdown-menu>  </div> <template> 

Dieser Code funktioniert aufgrund einer Änderung in $parent nicht. Die dropdown Komponente ist nicht mehr das übergeordnete dropdown-menu des dropdown-menu . Jetzt ist das dropdown-menu die transition .

Eigenschaften werden in der Komponentenhierarchie weitergegeben, Ereignisse werden weitergegeben. Diese Wörter enthalten die Bedeutung des richtigen Ansatzes zur Lösung unseres Problems. Hier ist ein Beispiel, das für Ereignisse geändert wurde.

 // Dropdown.vue ( ) <template>  <div>    <button @click="showMenu = !showMenu">Click me</button>    <dropdown-menu v-if="showMenu" :items="items" @select-option="onOptionSelected"></dropdown-menu>  </div> <template> export default {  props: {   items: Array  },  data() {   return {     selectedOption: null,     showMenu: false   }  },  methods: {    onOptionSelected(option) {      this.selectedOption = option      this.showMenu = true    }  } } // DropdownMenu.vue ( ) <template>  <ul>    <li v-for="item in items" @click="selectOption(item)">{{item.name}}</li>  </ul> </template> export default {  props: {   items: Array  },  methods: {    selectOption(item) {     this.$emit('select-option', item)    }  } } 

Dank der Verwendung von Ereignissen ist die untergeordnete Komponente nicht mehr an die übergeordnete Komponente gebunden. Wir können Eigenschaften mit Daten in der übergeordneten Komponente frei ändern und animierte Übergänge verwenden. Wir denken jedoch möglicherweise nicht darüber nach, wie sich unser Code auf die übergeordnete Komponente auswirken kann. Wir benachrichtigen diese Komponente einfach über das, was passiert ist. In diesem Fall dropdown die dropdown Komponente selbst Entscheidungen darüber, wie die Auswahl eines dropdown den Benutzer und das Schließen des Menüs behandelt werden sollen.

Zusammenfassung


Der kürzeste Code ist nicht immer der erfolgreichste. Entwicklungstechniken mit „einfachen und schnellen“ Ergebnissen sind häufig fehlerhaft. Um eine Programmiersprache, Bibliothek oder ein Framework richtig verwenden zu können, benötigen Sie Geduld und Zeit. Dies gilt für Vue.js.

Liebe Leser! Haben Sie in der Praxis Probleme festgestellt, wie sie in diesem Artikel beschrieben werden?

Source: https://habr.com/ru/post/de463327/


All Articles