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.
FehlermeldungFalsche Bestellwertberechnung für einen speziellen KundenIn 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.
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.
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
?
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.
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?
