Vue.js untuk pemula pelajaran 11: tab, bus acara global

Hari ini, dalam pelajaran ke-11 yang mengakhiri tutorial Dasar-dasar Vue ini, kita akan membahas tentang cara mengatur konten halaman aplikasi Anda menggunakan tab. Di sini kita akan membahas bus acara global - mekanisme sederhana untuk mentransfer data dalam aplikasi.







Vue.js pemula pelajaran 1: contoh Vue

Vue.js untuk pemula, pelajaran 2: mengikat atribut

Vue.js pemula pelajaran 3: rendering bersyarat

Vue.js pemula pelajaran 4: daftar rendering

Vue .js untuk pemula pelajaran 5: pemrosesan acara

Vue.js pemula pelajaran 6: mengikat kelas dan gaya

Vue.js pemula pelajaran 7: properti terhitung

Pelajaran pemula Vue.js 8: komponen

Vue. js untuk pemula pelajaran 9: acara khusus

Vue.js untuk pemula pelajaran 10: formulir



Tujuan pelajaran



Kami ingin memiliki tab pada halaman aplikasi, salah satunya memungkinkan pengunjung untuk menulis ulasan tentang produk, dan yang lainnya memungkinkan mereka untuk melihat ulasan yang ada.



Kode awal



Beginilah tampilan konten file pada tahap kerja ini index.html:



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

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


Di main.jssana ada kode berikut:



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


Seperti inilah tampilan aplikasinya sekarang.





Halaman aplikasi



Tugas



Saat ini, review dan formulir yang digunakan untuk mengirimkan review ditampilkan pada halaman yang bersebelahan. Ini adalah struktur kerja yang bagus. Tetapi semakin banyak ulasan yang diharapkan muncul di halaman seiring waktu. Ini berarti akan lebih mudah bagi pengguna untuk berinteraksi dengan halaman yang, sesuai pilihan mereka, menampilkan formulir atau daftar ulasan.



Solusi dari masalah tersebut



Untuk mengatasi masalah kami, kami dapat menambahkan sistem tab ke halaman. Salah satunya, dengan judul Reviews, akan menampilkan review. Yang kedua, dengan judul Make a Review, akan menampilkan formulir untuk mengirimkan ulasan.



Membuat komponen yang mengimplementasikan sistem tab



Mari kita mulai dengan membuat sebuah komponen product-tabs. Ini akan ditampilkan di bagian bawah representasi visual komponen product. Seiring waktu, ini akan menggantikan kode yang saat ini digunakan untuk menampilkan daftar ulasan dan formulir di halaman.



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']      
    }
  }
})


Saat ini, ini hanyalah komponen kosong yang akan segera kami selesaikan. Untuk saat ini, mari kita bahas secara singkat apa yang disajikan dalam kode ini.



Data komponen memiliki larik yang tabsberisi string yang kita gunakan sebagai tajuk tab. Template komponen menggunakan konstruksi v-foryang tabsmembuat elemen yang <span>berisi string terkait untuk setiap elemen array . Apa bentuk komponen ini pada tahap pekerjaan ini akan terlihat seperti yang ditunjukkan di bawah ini.





Komponen tab-produk pada tahap awal pengerjaannya



. Untuk mencapai tujuan kita, kita perlu mengetahui tab mana yang aktif. Oleh karena itu, mari tambahkan properti ke data komponenselectedTab. Kami akan secara dinamis menetapkan nilai properti ini menggunakan event handler yang merespons klik pada judul tab:



@click="selectedTab = tab"


Properti akan ditulis baris yang sesuai dengan judul tab.



Artinya, jika pengguna mengklik tab Reviews, selectedTabsebuah string akan ditulis Reviews. Jika Anda mengklik tab Make a Review, selectedTabbaris tersebut akan disertakan Make a Review.



Seperti inilah tampilan kode komponen lengkap sekarang.



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


Mengikat kelas ke tab aktif



Pengguna yang bekerja dengan antarmuka yang menggunakan tab harus mengetahui tab mana yang aktif. Anda bisa mengimplementasikan mekanisme serupa dengan menggunakan class binding ke elemen yang <span>digunakan untuk menampilkan nama tab:



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


Berikut adalah file CSS yang mendefinisikan gaya kelas yang digunakan di sini activeTab. Seperti inilah gaya ini:



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


Dan inilah gaya kelasnya tab:



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


Jika kami menjelaskan konstruksi di atas dalam bahasa sederhana, maka gaya yang ditentukan untuk kelas diterapkan ke tab activeTab, dalam kasus ketika selectedTabsama tab. Karena selectedTabnama tab yang baru saja diklik pengguna tertulis, gaya .activeTabakan diterapkan secara khusus ke tab aktif.



Dengan kata lain, ketika pengguna mengklik pada tab pertama, tabakan ditemukan Reviews, hal yang sama akan ditulis selectedTab. Hasilnya, gaya akan diterapkan ke tab pertama .activeTab.



Sekarang judul tab pada halaman tersebut akan terlihat seperti di bawah ini.





Judul yang disorot dari tab aktif



Sepertinya semuanya bekerja seperti yang diharapkan pada tahap ini, jadi kita bisa melanjutkan.



Mengerjakan template komponen



Sekarang kita bisa memberi tahu pengguna tab mana yang aktif, kita bisa melanjutkan mengerjakan komponen. Yakni, kita berbicara tentang menyelesaikan templatnya, menjelaskan apa sebenarnya yang akan ditampilkan pada halaman ketika masing-masing tab diaktifkan.



Mari pikirkan tentang apa yang harus ditampilkan kepada pengguna jika dia mengklik tab Reviews. Ini, tentu saja, adalah ulasan produk. Oleh karena itu, mari kita pindahkan kode untuk menampilkan review dari template komponen productke template komponen product-tabs, letakkan kode ini di bawah konstruksi yang digunakan untuk menampilkan header tab. Seperti inilah tampilan template komponen sekarang 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>
`


Perhatikan bahwa kami menghilangkan tag <h2><font color="#3AC1EF">karena kami tidak perlu lagi menampilkan judul di Reviewsatas daftar ulasan. Alih-alih judul ini, judul tab terkait akan ditampilkan.



Tetapi memindahkan kode template saja tidak cukup untuk memberikan umpan balik. Larik reviewsyang datanya digunakan untuk menampilkan tinjauan disimpan sebagai bagian dari data komponen product. Kita perlu meneruskan array ini ke komponen product-tabsmenggunakan mekanisme props komponen . Mari tambahkan yang product-tabsberikut ini ke objek dengan opsi yang digunakan selama pembuatan :



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


Mari kita lewati array reviewsdari komponen productke komponen product-tabsmenggunakan productkonstruksi berikut di template :



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


Sekarang mari kita pikirkan tentang apa yang perlu ditampilkan pada halaman jika pengguna mengklik judul tab Make a Review. Ini, tentu saja, merupakan formulir untuk mengirimkan umpan balik. Untuk mempersiapkan proyek untuk pengerjaan lebih lanjut, mari transfer kode koneksi komponen product-reviewdari template komponen productke template product-tabs. Mari letakkan kode berikut di bawah elemen yang <div>digunakan untuk menampilkan daftar ulasan:



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


Jika Anda melihat halaman aplikasi sekarang, Anda akan menemukan bahwa daftar review dan formulir ditampilkan di atasnya di bawah tajuk tab.





Tahap menengah dalam mengerjakan halaman



Dalam kasus ini, klik pada judul, meskipun mengarah ke pilihannya, tidak mempengaruhi elemen halaman lainnya dengan cara apa pun. Selanjutnya, jika Anda mencoba menggunakan formulir tersebut, ternyata sudah berhenti berfungsi secara normal. Semua ini merupakan konsekuensi yang diharapkan dari perubahan yang kami lakukan pada aplikasi. Mari terus bekerja dan membawa proyek kita ke status kerja.



Tampilan bersyarat dari elemen halaman



Sekarang setelah kita menyiapkan elemen dasar template komponen product-tabs, sekarang saatnya membuat sistem yang memungkinkan Anda menampilkan elemen halaman yang berbeda berdasarkan judul tab yang diklik pengguna.



Data komponen sudah memiliki properti selectedTab. Kita dapat menggunakannya dalam arahan v-showuntuk membuat secara bersyarat apa yang menjadi milik masing-masing tab.



Jadi, ke tag yang <div>berisi kode untuk membuat daftar ulasan, kita dapat menambahkan konstruksi berikut:



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


Berkat dia, daftar ulasan akan ditampilkan saat tab aktif Reviews.



Demikian pula, kami akan menambahkan yang berikut ini ke tag yang <div>berisi kode koneksi komponen product-review:



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


Ini akan mengakibatkan formulir hanya ditampilkan saat tab aktif Make a Review.



Seperti inilah tampilan template komponen sekarang 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>
`


Jika Anda melihat halaman dan mengklik tab, Anda dapat memastikan bahwa mekanisme yang kami buat berfungsi dengan benar.





Mengklik pada tab menyembunyikan beberapa elemen dan menampilkan yang lain.



Mengirimkan umpan balik melalui formulir masih tidak berhasil. Mari selidiki masalahnya dan perbaiki.



Memecahkan masalah dengan mengirimkan umpan balik



Jika Anda melihat konsol alat pengembang browser sekarang, Anda dapat melihat peringatan.





Peringatan Konsol



Jelas, sistem tidak dapat mendeteksi metode iniaddReview. Apa yang terjadi padanya?



Untuk menjawab pertanyaan ini, ingatlah bahwa iniaddReviewadalah metode yang dideklarasikan dalam sebuah komponenproduct. Ini harus dipanggil jika komponenproduct-review(dan ini adalah komponen anak dari komponenproduct) menghasilkan peristiwareview-submitted:



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


Beginilah semuanya bekerja sebelum mentransfer potongan kode di atas ke komponen product-tabs. Dan sekarang komponen productadalah komponen anak product-tabs, dan product-reviewsekarang bukan "anak", komponen product, tapi "cucu" nya.



Kode kami sekarang dirancang untuk berinteraksi product-reviewdengan komponen induk. Tapi sekarang tidak lagi menjadi komponen product. Hasilnya, ternyata agar formulir berfungsi dengan benar, kita perlu melakukan refaktorisasi kode proyek.



Refactoring kode proyek



Untuk memastikan komunikasi komponen cucu dengan "kakek nenek" mereka, atau untuk menjalin komunikasi antar komponen pada level yang sama, mekanisme yang disebut bus peristiwa global sering digunakan.



Global Event Bus adalah saluran komunikasi yang dapat digunakan untuk mentransfer informasi antar komponen. Dan ini, pada kenyataannya, hanyalah sebuah instance Vue yang dibuat tanpa memberikannya sebuah objek dengan opsi. Mari buat bus acara:



var eventBus = new Vue()


Kode ini akan ditempatkan di bagian atas file main.js.



Anda mungkin akan lebih mudah memahami konsep ini jika menganggap bus acara sebagai bus. Penumpangnya adalah data yang dikirim beberapa komponen ke komponen lain. Dalam kasus kami, kami berbicara tentang mentransfer informasi tentang peristiwa yang dihasilkan oleh komponen lain ke satu komponen. Artinya, "bus" kami akan melakukan perjalanan dari komponen product-reviewke komponen product, membawa informasi bahwa formulir telah diserahkan dan mengirimkan data formulir product-reviewke product.



Sekarang di dalam komponen product-review, di dalam metode onSubmit, ada baris seperti ini:



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


Kami akan menggantinya dengan yang berikut ini, eventBussebagai gantinya this:



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


Setelah itu, Anda tidak perlu lagi mendengarkan event review-submittedkomponen product-review. Oleh karena itu, kami akan mengubah kode komponen ini di template komponen product-tabsmenjadi berikut:



<product-review></product-review>


productMetode tersebut sekarang dapat dihapus dari komponen addReview. Sebagai gantinya, kami akan menggunakan konstruksi berikut:



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


Kami akan berbicara di bawah tentang cara menggunakannya dalam sebuah komponen, tetapi untuk saat ini, kami akan menjelaskan secara singkat apa yang terjadi di dalamnya. Konstruksi ini menunjukkan bahwa ketika ia eventBusmenghasilkan sebuah peristiwa review-submitted, Anda perlu mengambil data yang diteruskan dalam peristiwa ini (yaitu, - productReview) dan menempatkannya dalam array reviewskomponen product. Faktanya, ini sangat mirip dengan apa yang telah dilakukan selama ini dengan metode addReviewyang tidak lagi kita butuhkan. Perhatikan bahwa potongan kode di atas menggunakan fungsi panah. Momen ini layak untuk liputan yang lebih rinci.



Alasan menggunakan fungsi panah



Di sini kami menggunakan sintaks fungsi panah yang diperkenalkan di ES6. Intinya adalah konteks fungsi panah terikat pada konteks induk. Artinya, ketika kita, di dalam fungsi ini, menggunakan kata kunci this, itu setara dengan kata kunci thisyang sesuai dengan entitas yang berisi fungsi panah.



Kode ini dapat ditulis ulang tanpa menggunakan fungsi panah, tetapi Anda perlu mengatur penjilidan this:



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


Menyelesaikan proyek



Kami hampir mencapai tujuan kami. Semua yang masih harus dilakukan adalah menemukan tempat untuk potongan kode yang memberikan respons terhadap peristiwa tersebut review-submitted. Suatu productfungsi bisa menjadi tempat seperti itu dalam sebuah komponen mounted:



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


Apa fungsi ini? Ini adalah pengait siklus hidup yang dipanggil satu kali setelah komponen dipasang di DOM. Sekarang, setelah komponen productdipasang, itu akan menunggu peristiwa terjadi review-submitted. Setelah peristiwa semacam itu dibuat, apa yang diteruskan dalam peristiwa ini akan ditambahkan ke data komponen, yaitu, - productReview.



Jika sekarang Anda mencoba meninggalkan review tentang produk menggunakan formulir, ternyata ulasan ini ditampilkan di tempat yang seharusnya.





Formulir berfungsi sebagaimana mestinya



Bus acara bukanlah solusi terbaik untuk mengkomunikasikan komponen



Meskipun bus acara sering digunakan, dan meskipun Anda mungkin menemukannya di berbagai proyek, perlu diingat bahwa ini jauh dari solusi terbaik untuk masalah menghubungkan komponen aplikasi.



Seiring perkembangan aplikasi, sistem manajemen status berdasarkan Vuex bisa sangat berguna . Ini adalah pola dan perpustakaan manajemen negara aplikasi.



Bengkel



Tambahkan tab Shippingdan ke proyek Details, yang masing-masing menampilkan biaya pengiriman pembelian dan informasi tentang barang.



  • Berikut adalah template yang dapat Anda gunakan untuk mengatasi masalah ini.
  • Inilah solusi untuk masalah tersebut.


Hasil



Inilah yang Anda pelajari dalam tutorial ini:



  • Anda dapat menggunakan alat rendering bersyarat untuk mengatur mekanisme tab.
  • , Vue, .
  • — . . — Vuex.


Kami berharap setelah mengikuti kursus Vue ini, Anda mempelajari apa yang Anda inginkan dan siap untuk mempelajari lebih banyak hal baru dan menarik tentang framework ini.



Jika Anda baru saja menyelesaikan kursus ini, silakan bagikan kesan Anda.



Vue.js pemula pelajaran 1: contoh Vue

Vue.js untuk pemula, pelajaran 2: mengikat atribut

Vue.js pemula pelajaran 3: rendering bersyarat

Vue.js pemula pelajaran 4: daftar rendering

Vue .js untuk Pemula Pelajaran 5: Penanganan Acara

Vue.js untuk Pemula, Pelajaran 6: Mengikat Kelas dan Gaya

Vue.js untuk Pemula, Pelajaran 7: Properti Terhitung

Vue.js untuk pemula, pelajaran 8: Komponen

Vue.js untuk pemula, pelajaran 9: acara khusus

Vue.js untuk pemula, pelajaran 10: Formulir






All Articles