Vue.js für Anfänger Lektion 11: Tabs, der globale Eventbus

In der 11. Lektion, die dieses Tutorial zu Vue Fundamentals abschließt, werden wir heute darüber sprechen, wie der Inhalt einer Anwendungsseite mithilfe von Registerkarten organisiert wird. Hier diskutieren wir den globalen Ereignisbus - einen einfachen Mechanismus zum Übertragen von Daten innerhalb einer Anwendung.







Vue.js für Anfänger Lektion 1: Vue-Instanz

Vue.js für Anfänger, Lektion 2: Attributbindung

Vue.js für Anfänger, Lektion 3: Bedingtes Rendern

Vue.js für Anfänger, Lektion 4: Rendern von Listen

Vue .js für Anfänger Lektion 5: Ereignisverarbeitung

Vue.js Anfänger Lektion 6: Binden von Klassen und Stilen

Vue.js Anfänger Lektion 7: berechnete Eigenschaften

Vue.js Anfänger Lektion 8: Komponenten

Vue. js für Anfänger Lektion 9: Benutzerdefinierte Ereignisse

Vue.js für Anfänger Lektion 10: Formulare



Der Zweck der Lektion



Wir möchten Registerkarten auf der Anwendungsseite haben, von denen eine den Besuchern ermöglicht, Bewertungen zu Produkten zu schreiben, und die andere es ihnen ermöglicht, vorhandene Bewertungen anzuzeigen.



Anfangscode



So sieht der Inhalt der Datei in dieser Arbeitsphase aus index.html:



<div id="app">
  <div class="cart">
    <p>Cart({{ cart.length }})</p>
  </div>

  <product :premium="premium" @add-to-cart="updateCart"></product>
</div>


Darin main.jsbefindet sich der folgende Code:



Vue.component('product', {
  props: {
    premium: {
      type: Boolean,
      required: true
    }
  },
  template: `
  <div class="product">

    <div class="product-image">
      <img :src="image" />
    </div>

    <div class="product-info">
      <h1>{{ title }}</h1>
      <p v-if="inStock">In stock</p>
      <p v-else>Out of Stock</p>
      <p>Shipping: {{ shipping }}</p>

      <ul>
        <li v-for="(detail, index) in details" :key="index">{{ detail }}</li>
      </ul>
      <div
        class="color-box"
        v-for="(variant, index) in variants"
        :key="variant.variantId"
        :style="{ backgroundColor: variant.variantColor }"
        @mouseover="updateProduct(index)"
      ></div>

      <button
        @click="addToCart"
        :disabled="!inStock"
        :class="{ disabledButton: !inStock }"
      >
        Add to cart
      </button>

    </div>

    <div>
      <h2><font color="#3AC1EF">Reviews</font></h2>
      <p v-if="!reviews.length">There are no reviews yet.</p>
      <ul>
        <li v-for="review in reviews">
        <p>{{ review.name }}</p>
        <p>Rating: {{ review.rating }}</p>
        <p>{{ review.review }}</p>
        </li>
      </ul>
    </div>

    <product-review @review-submitted="addReview"></product-review>   
  
    </div>
  `,
  data() {
    return {
      product: 'Socks',
      brand: 'Vue Mastery',
      selectedVariant: 0,
      details: ['80% cotton', '20% polyester', 'Gender-neutral'],
      variants: [
        {
          variantId: 2234,
          variantColor: 'green',
          variantImage: './assets/vmSocks-green.jpg',
          variantQuantity: 10
        },
        {
          variantId: 2235,
          variantColor: 'blue',
          variantImage: './assets/vmSocks-blue.jpg',
          variantQuantity: 0
        }
      ],
      reviews: []
    }
  },
    methods: {
      addToCart() {
        this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId);
      },
      updateProduct(index) {
        this.selectedVariant = index;
      },
      addReview(productReview) {
        this.reviews.push(productReview)
      }
    },
    computed: {
      title() {
        return this.brand + ' ' + this.product;
      },
      image() {
        return this.variants[this.selectedVariant].variantImage;
      },
      inStock() {
        return this.variants[this.selectedVariant].variantQuantity;
      },
      shipping() {
        if (this.premium) {
          return "Free";
        } else {
          return 2.99
        }
      }
    }
})

Vue.component('product-review', {
  template: `
    <form class="review-form" @submit.prevent="onSubmit">

      <p v-if="errors.length">
        <b>Please correct the following error(s):</b>
        <ul>
          <li v-for="error in errors">{{ error }}</li>
        </ul>
      </p>

      <p>
        <label for="name">Name:</label>
        <input id="name" v-model="name">
      </p>
      
      <p>
        <label for="review">Review:</label>      
        <textarea id="review" v-model="review"></textarea>
      </p>
      
      <p>
        <label for="rating">Rating:</label>
        <select id="rating" v-model.number="rating">
          <option>5</option>
          <option>4</option>
          <option>3</option>
          <option>2</option>
          <option>1</option>
        </select>
      </p>
          
      <p>
        <input type="submit" value="Submit">  
      </p>    

    </form>

  `,
  data() {
    return {
      name: null,
      review: null,
      rating: null,
      errors: []
    }
  },
  methods: {
    onSubmit() {
      if(this.name && this.review && this.rating) {
        let productReview = {
          name: this.name,
          review: this.review,
          rating: this.rating
        }
        this.$emit('review-submitted', productReview)
        this.name = null
        this.review = null
        this.rating = null
      } else {
        if(!this.name) this.errors.push("Name required.")
        if(!this.review) this.errors.push("Review required.")
        if(!this.rating) this.errors.push("Rating required.")
      }
    }
  }
})

var app = new Vue({
  el: '#app',
  data: {
    premium: true,
    cart: []
  },
  methods: {
    updateCart(id) {
      this.cart.push(id);
    }
  }
})


So sieht die Anwendung jetzt aus.





Bewerbungsseite



Aufgabe



Derzeit werden die Bewertungen und das Formular, mit dem Bewertungen eingereicht werden, auf der Seite nebeneinander angezeigt. Dies ist eine ziemlich funktionierende Struktur. Es wird jedoch erwartet, dass im Laufe der Zeit immer mehr Bewertungen auf der Seite erscheinen. Dies bedeutet, dass Benutzer bequemer mit einer Seite interagieren können, auf der nach ihrer Wahl entweder ein Formular oder eine Liste von Bewertungen angezeigt wird.



Die Lösung des Problems



Um unser Problem zu lösen, können wir der Seite ein System von Registerkarten hinzufügen. Einer von ihnen mit einem Titel Reviewszeigt Rezensionen an. Die zweite mit einem Titel Make a Reviewzeigt ein Formular zum Einreichen von Bewertungen an.



Erstellen einer Komponente, die das Registerkartensystem implementiert



Beginnen wir mit der Erstellung einer Komponente product-tabs. Es wird am unteren Rand der visuellen Darstellung der Komponente angezeigt product. Im Laufe der Zeit wird der Code ersetzt, der derzeit zum Anzeigen der Liste der Überprüfungen und Formulare auf der Seite verwendet wird.



Vue.component('product-tabs', {
  template: `
    <div>
      <span class="tab" v-for="(tab, index) in tabs" :key="index">{{ tab }}</span>
    </div>
  `,
  data() {
    return {
      tabs: ['Reviews', 'Make a Review']      
    }
  }
})


Im Moment ist dies nur eine leere Komponente, die wir bald fertigstellen werden. Lassen Sie uns zunächst kurz diskutieren, was in diesem Code dargestellt wird.



Die Komponentendaten enthalten ein Array tabsmit den Zeichenfolgen, die wir als Registerkartenüberschriften verwenden. Die Komponentenvorlage verwendet eine Konstruktion v-for, tabsdie ein Element erstellt, <span>das die entsprechende Zeichenfolge für jedes Arrayelement enthält . Was diese Komponente in dieser Phase der Arbeit daran bildet, sieht wie folgt aus.





Die Produkt-Registerkarten-Komponente in der Anfangsphase der Arbeit daran



. Um unsere Ziele zu erreichen, müssen wir wissen, welche der Registerkarten aktiv ist. Fügen wir daher den Komponentendaten eine Eigenschaft hinzuselectedTab. Wir werden den Wert dieser Eigenschaft mithilfe eines Ereignishandlers dynamisch festlegen, der auf Klicks auf die Tabulatortitel reagiert:



@click="selectedTab = tab"


Die Eigenschaft besteht aus Zeilen, die den Titeln der Registerkarten entsprechen.



Das heißt, wenn der Benutzer auf die Registerkarte klickt Reviews, wird selectedTabeine Zeichenfolge geschrieben Reviews. Wenn Sie auf die Registerkarte klicken Make a Review, wird die selectedTabZeile eingefügt Make a Review.



So sieht der vollständige Komponentencode jetzt aus.



Vue.component('product-tabs', {
  template: `
    <div>    
      <ul>
        <span class="tab" 
              v-for="(tab, index) in tabs" 
              @click="selectedTab = tab"
        >{{ tab }}</span>
      </ul> 
    </div>
  `,
  data() {
    return {
      tabs: ['Reviews', 'Make a Review'],
      selectedTab: 'Reviews'  //    @click
    }
  }
})


Binden einer Klasse an eine aktive Registerkarte



Ein Benutzer, der mit einer Schnittstelle arbeitet, die Registerkarten verwendet, muss wissen, welche Registerkarte aktiv ist. Sie können einen ähnlichen Mechanismus mithilfe der Klassenbindung an Elemente implementieren , <span>die zum Anzeigen von Registerkartennamen verwendet werden:



:class="{ activeTab: selectedTab === tab }"


Hier ist die CSS-Datei, die den Stil der hier verwendeten Klasse definiert activeTab. So sieht dieser Stil aus:



.activeTab {
  color: #16C0B0;
  text-decoration: underline;
}


Und hier ist der Klassenstil tab:



.tab {
  margin-left: 20px;
  cursor: pointer;
}


Wenn wir die obige Konstruktion in einfacher Sprache erklären, stellt sich heraus, dass der für die Klasse angegebene Stil auf die Registerkarte angewendet wird, activeTabfalls selectedTabgleich tab. Da der selectedTabName der Registerkarte, auf die der Benutzer gerade geklickt hat, geschrieben ist, wird der Stil .activeTabspeziell auf die aktive Registerkarte angewendet.



Mit anderen Worten, wenn der Benutzer auf die erste Registerkarte klickt, tabwird in Reviewsdasselbe geschrieben selectedTab. Infolgedessen wird der Stil auf die erste Registerkarte angewendet .activeTab.



Jetzt sehen die Tab-Titel auf der Seite wie folgt aus.





Der hervorgehobene Titel der aktiven Registerkarte Es



sieht so aus, als ob zu diesem Zeitpunkt alles wie erwartet funktioniert, sodass wir weitermachen können.



Arbeiten an der Komponentenvorlage



Nachdem wir dem Benutzer mitteilen können, welche Registerkarte aktiv ist, können wir weiter an der Komponente arbeiten. Wir sprechen nämlich über die Fertigstellung der Vorlage und beschreiben, was genau auf der Seite angezeigt wird, wenn jede der Registerkarten aktiviert wird.



Überlegen wir uns, was dem Benutzer angezeigt werden soll, wenn er auf die Registerkarte klickt Reviews. Dies sind natürlich Produktbewertungen. Deshalb werden wir den Code bewegen auf Bewertungen aus der Komponentenvorlage auf die Komponente Anzeige productVorlage product-tabs, platzieren Sie diesen Code unter der Konstruktion verwendet , um die Registerkarte Header anzuzeigen. So sieht die Komponentenvorlage jetzt aus product-tabs:



template: `
  <div>    
    <ul>
      <span class="tab"
            :class="{ activeTab: selectedTab === tab }" 
            v-for="(tab, index) in tabs" 
            @click="selectedTab = tab"
      >{{ tab }}</span>
    </ul> 
    <div>
      <p v-if="!reviews.length">There are no reviews yet.</p>
      <ul>
        <li v-for="review in reviews">
        <p>{{ review.name }}</p>
        <p>Rating: {{ review.rating }}</p>
        <p>{{ review.review }}</p>
        </li>
      </ul>
    </div>
  </div>
`


Beachten Sie, dass wir das Tag entfernt haben, <h2><font color="#3AC1EF">da wir den Titel nicht mehr Reviewsüber der Liste der Bewertungen anzeigen müssen . Anstelle dieses Titels wird der Titel der entsprechenden Registerkarte angezeigt.



Das Verschieben des Vorlagencodes allein reicht jedoch nicht aus, um Feedback zu geben. Das Array, reviewsdessen Daten zum Anzeigen von Bewertungen verwendet werden, wird als Teil der Komponentendaten gespeichert product. Wir müssen dieses Array product-tabsmithilfe des Komponenten- Requisitenmechanismus an die Komponente übergeben . Fügen wir dem Objekt product-tabsFolgendes mit Optionen hinzu, die während der Erstellung verwendet werden :



props: {
  reviews: {
    type: Array,
    required: false
  }
}


Übergeben wir ein Array reviewsvon Komponente productzu Komponente product-tabsmithilfe der productfolgenden Konstruktion in der Vorlage :



<product-tabs :reviews="reviews"></product-tabs>


Überlegen wir uns nun, was auf der Seite angezeigt werden muss, wenn der Benutzer auf den Titel der Registerkarte klickt Make a Review. Dies ist natürlich ein Formular zum Einreichen von Feedback. Um das Projekt für die weitere Arbeit vorzubereiten, übertragen wir den Komponentenverbindungscode product-reviewvon der Komponentenvorlage productin die Vorlage product-tabs. Platzieren Sie den folgenden Code unter dem Element <div>, mit dem die Liste der Bewertungen angezeigt wird:



<div>
  <product-review @review-submitted="addReview"></product-review>
</div>


Wenn Sie sich jetzt die Anwendungsseite ansehen, werden Sie feststellen, dass die Liste der Bewertungen und das Formular unter den Registerkartenüberschriften angezeigt werden.





Eine Zwischenstufe der Bearbeitung der Seite



In diesem Fall wirken sich Klicks auf die Überschriften, obwohl sie zu ihrer Auswahl führen, in keiner Weise auf andere Elemente der Seite aus. Wenn Sie versuchen, das Formular zu verwenden, stellt sich heraus, dass es nicht mehr normal funktioniert. All dies sind durchaus erwartete Konsequenzen der Änderungen, die wir an der Anwendung vorgenommen haben. Lassen Sie uns weiterarbeiten und unser Projekt in einen funktionierenden Zustand bringen.



Bedingte Anzeige von Seitenelementen



Nachdem wir die Hauptelemente der Komponentenvorlage vorbereitet haben product-tabs, ist es Zeit, ein System zu erstellen, mit dem Sie verschiedene Seitenelemente anzeigen können, basierend auf dem Tabulatortitel, auf den der Benutzer geklickt hat.



Die Komponentendaten haben bereits eine Eigenschaft selectedTab. Wir können es in einer Direktive verwenden, v-showum bedingt zu rendern, was zu jeder der Registerkarten gehört.



Daher <div>können wir dem Tag, das den Code zum Generieren der Liste der Bewertungen enthält, die folgende Konstruktion hinzufügen:



v-show="selectedTab === 'Reviews'"


Dank ihr wird die Liste der Bewertungen angezeigt, wenn die Registerkarte aktiv ist Reviews.



In ähnlicher Weise fügen wir dem Tag <div>, das den Komponentenverbindungscode enthält, product-reviewFolgendes hinzu:



v-show="selectedTab === 'Make a Review'"


Dies führt dazu, dass das Formular nur angezeigt wird, wenn die Registerkarte aktiv ist Make a Review.



So sieht die Komponentenvorlage jetzt aus product-tabs:



template: `
  <div>    
    <ul>
      <span class="tab"
            :class="{ activeTab: selectedTab === tab }" 
            v-for="(tab, index) in tabs" 
            @click="selectedTab = tab"
      >{{ tab }}</span>
    </ul> 
    <div v-show="selectedTab === 'Reviews'">
      <p v-if="!reviews.length">There are no reviews yet.</p>
      <ul>
        <li v-for="review in reviews">
        <p>{{ review.name }}</p>
        <p>Rating: {{ review.rating }}</p>
        <p>{{ review.review }}</p>
        </li>
      </ul>
    </div>
    <div v-show="selectedTab === 'Make a Review'">
      <product-review @review-submitted="addReview"></product-review>
    </div>
  </div>
`


Wenn Sie sich die Seite ansehen und auf die Registerkarten klicken, können Sie sicherstellen, dass der von uns erstellte Mechanismus ordnungsgemäß funktioniert.





Durch Klicken auf die Registerkarten werden einige Elemente



ausgeblendet und andere angezeigt . Das Senden von Feedback über ein Formular funktioniert immer noch nicht. Lassen Sie uns das Problem untersuchen und beheben.



Lösen des Problems mit dem Senden von Feedback



Wenn Sie sich jetzt die Konsole der Browser-Entwicklertools ansehen, wird eine Warnung angezeigt.





Konsolenwarnung



Offensichtlich kann das System die Methode nicht erkennenaddReview. Was ist mit ihm passiert?



Um diese Frage zu beantworten, denken wir daran, dass es sichaddReviewum eine Methode handelt, die in einer Komponente deklariert istproduct. Es sollte aufgerufen werden, wenn die Komponenteproduct-review(und dies ist eine untergeordnete Komponente der Komponenteproduct) ein Ereignis generiertreview-submitted:



<product-review @review-submitted="addReview"></product-review>


So funktionierte alles, bevor das obige Code-Snippet auf die Komponente übertragen wurde product-tabs. Und jetzt ist eine Komponente producteine untergeordnete Komponente product-tabs, und product-reviewjetzt ist sie kein „Kind“, eine Komponente product, sondern ihr „Enkel“.



Unser Code ist jetzt so konzipiert, dass er product-reviewmit der übergeordneten Komponente interagiert . Aber jetzt ist es keine Komponente mehr product. Infolgedessen stellt sich heraus, dass wir den Projektcode umgestalten müssen, damit das Formular ordnungsgemäß funktioniert.



Projektcode umgestalten



Um die Kommunikation von Enkelkomponenten mit ihren "Großeltern" sicherzustellen oder um die Kommunikation zwischen Komponenten derselben Ebene herzustellen, wird häufig ein Mechanismus verwendet, der als globaler Ereignisbus bezeichnet wird.



Der Global Event Bus ist ein Kommunikationskanal, über den Informationen zwischen Komponenten übertragen werden können. Tatsächlich handelt es sich nur um eine Vue-Instanz, die erstellt wird, ohne dass ein Objekt mit Optionen übergeben wird. Erstellen wir einen Ereignisbus:



var eventBus = new Vue()


Dieser Code wird in die oberste Ebene der Datei verschoben main.js.



Möglicherweise fällt es Ihnen leichter, dieses Konzept zu verstehen, wenn Sie sich den Ereignisbus als Bus vorstellen. Die Passagiere sind Daten, die einige Komponenten an andere senden. In unserem Fall geht es darum, Informationen über Ereignisse, die von anderen Komponenten generiert wurden, an eine Komponente zu übertragen. Das heißt, unser "Bus" fährt von Komponente product-reviewzu Komponente product, trägt Informationen darüber, dass das Formular gesendet wurde, und liefert die Formulardaten von product-reviewan product.



Jetzt in der Komponente product-review, in der Methode onSubmit, gibt es eine Zeile wie folgt:



this.$emit('review-submitted', productReview)


Wir werden es durch Folgendes ersetzen und eventBusstattdessen verwenden this:



eventBus.$emit('review-submitted', productReview)


Danach müssen Sie das review-submittedKomponentenereignis nicht mehr abhören product-review. Daher ändern wir den Code dieser Komponente in der Komponentenvorlage product-tabswie folgt:



<product-review></product-review>


Die productMethode kann jetzt aus der Komponente entfernt werden addReview. Stattdessen verwenden wir die folgende Konstruktion:



eventBus.$on('review-submitted', productReview => {
  this.reviews.push(productReview)
})


Wir werden weiter unten darüber sprechen, wie man es in einer Komponente verwendet, aber im Moment werden wir kurz beschreiben, was darin passiert. Dieses Konstrukt gibt an, dass Sie beim eventBusGenerieren eines Ereignisses review-submitteddie in diesem Ereignis übergebenen Daten (dh - productReview) in das reviewsKomponentenarray einfügen müssen product. Tatsächlich ist dies sehr ähnlich zu dem, was bisher in einer Methode gemacht wurde addReview, die wir nicht mehr brauchen. Beachten Sie, dass das obige Codefragment eine Pfeilfunktion verwendet. Dieser Moment verdient eine detailliertere Berichterstattung.



Gründe für die Verwendung einer Pfeilfunktion



Hier verwenden wir den Pfeil Funktion Syntax , die in ES6 eingeführt wurde. Der Punkt ist, dass der Kontext der Pfeilfunktion an den übergeordneten Kontext gebunden ist. Wenn wir in dieser Funktion ein Schlüsselwort verwenden this, entspricht dies dem Schlüsselwort this, das der Entität entspricht, die die Pfeilfunktion enthält.



Dieser Code kann ohne Verwendung von Pfeilfunktionen neu geschrieben werden, aber dann müssen Sie die Bindung organisieren this:



eventBus.$on('review-submitted', function (productReview) {
  this.reviews.push(productReview)
}.bind(this))


Projekt abschließen



Wir haben unser Ziel fast erreicht. Alles, was noch getan werden muss, ist, einen Platz für den Code zu finden, der eine Antwort auf das Ereignis liefert review-submitted. Eine productFunktion kann zu einem solchen Ort in einer Komponente werden mounted:



mounted() {
  eventBus.$on('review-submitted', productReview => {
    this.reviews.push(productReview)
  })
}


Was ist diese Funktion? Dies ist ein Lebenszyklus-Hook , der einmal aufgerufen wird, nachdem die Komponente im DOM bereitgestellt wurde. Nachdem die Komponente productbereitgestellt wurde, wartet sie nun auf das Auftreten von Ereignissen review-submitted. Nachdem ein solches Ereignis generiert wurde, wird das, was in diesem Ereignis übergeben wird, zu den Komponentendaten hinzugefügt, d productReview. H.



Wenn Sie jetzt versuchen, mithilfe des Formulars eine Bewertung zum Produkt abzugeben, wird diese Bewertung dort angezeigt, wo sie sein sollte.





Form funktioniert wie es sollte



Der Ereignisbus ist nicht die beste Lösung für die Kommunikation von Komponenten



Obwohl der Ereignisbus häufig verwendet wird und Sie ihn möglicherweise in verschiedenen Projekten finden, sollten Sie bedenken, dass dies bei weitem nicht die beste Lösung für das Problem des Verbindens von Anwendungskomponenten ist.



Wenn die Anwendung wächst, kann ein auf Vuex basierendes Statusverwaltungssystem sehr nützlich sein . Es ist ein Muster und eine Bibliothek zur Verwaltung des Anwendungsstatus.



Werkstatt



Fügen Sie dem Projekt Registerkarten Shippingund hinzu Details, in denen die Kosten für die Lieferung von Einkäufen und Informationen zu Waren angezeigt werden.





Ergebnis



Folgendes haben Sie in diesem Tutorial gelernt:



  • Sie können Tools zum bedingten Rendern verwenden, um den Registerkartenmechanismus zu organisieren.
  • , Vue, .
  • — . . — Vuex.


Wir hoffen, dass Sie nach dem Besuch dieses Vue-Kurses gelernt haben, was Sie wollten, und bereit sind, viel mehr neue und interessante Dinge über dieses Framework zu lernen.



Wenn Sie diesen Kurs gerade abgeschlossen haben, teilen Sie uns bitte Ihre Eindrücke mit.



Vue.js für Anfänger Lektion 1: Vue-Instanz

Vue.js für Anfänger, Lektion 2: Attributbindung

Vue.js für Anfänger, Lektion 3: Bedingtes Rendern

Vue.js für Anfänger, Lektion 4: Rendern von Listen

Vue .js für Anfänger Lektion 5: Ereignisbehandlung

Vue.js für Anfänger, Lektion 6: Bindungsklassen und -stile

Vue.js für Anfänger, Lektion 7: Berechnete Eigenschaften

Vue.js für Anfänger, Lektion 8: Komponenten

Vue.js für Anfänger, Lektion 9: Benutzerdefinierte Ereignisse

Vue.js für Anfänger, Lektion 10: Formulare






All Articles