Vue.js: 3 anti-pola

Vue.js mungkin merupakan salah satu kerangka kerja JavaScript terbaik. Ini memiliki API yang intuitif, cepat, fleksibel, dan mudah digunakan. Namun, fleksibilitas Vue.js hadir dengan bahaya tertentu. Beberapa pengembang yang bekerja dengan kerangka kerja ini rentan terhadap pengawasan kecil. Ini dapat mempengaruhi kinerja aplikasi, atau, dalam jangka panjang, kemampuan untuk mendukung mereka.



Penulis materi, terjemahan yang kami terbitkan hari ini, menawarkan untuk mengurai beberapa kesalahan umum yang dilakukan oleh mereka yang mengembangkan aplikasi di Vue.js.

Efek samping di dalam properti yang dihitung


Properti yang dikomputasi adalah mekanisme Vue.js yang sangat nyaman yang memungkinkan Anda untuk mengatur pekerjaan dengan fragmen negara yang bergantung pada fragmen negara lainnya. Properti yang dikomputasi hanya boleh digunakan untuk menampilkan data yang disimpan dalam keadaan yang tergantung pada data lain dari negara. Jika ternyata Anda memanggil beberapa metode di dalam properti yang dihitung atau menulis beberapa nilai ke variabel status lainnya, ini mungkin berarti Anda melakukan sesuatu yang salah. Pertimbangkan sebuah contoh.

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

Jika kami mencoba mencetak array dan reversedArray array , kami akan melihat bahwa kedua array berisi nilai yang sama.

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

Ini karena properti reversedArray dihitung memodifikasi properti array asli dengan memanggil metode .reverse() . Ini adalah contoh yang cukup sederhana yang menunjukkan perilaku sistem yang tidak terduga. Lihatlah contoh lain.

Misalkan kita memiliki komponen yang menampilkan informasi terperinci tentang harga barang atau jasa yang termasuk dalam urutan tertentu.

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

Di sini kami telah membuat properti yang dihitung yang menampilkan total biaya pesanan, termasuk pajak dan diskon. Karena kami tahu bahwa nilai total pesanan berubah di sini, kami dapat mencoba meningkatkan peristiwa yang memberi tahu komponen induk tentang perubahan 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        };      }    }  } }; 

Sekarang bayangkan bahwa kadang-kadang, meskipun sangat jarang, muncul situasi di mana kami bekerja dengan pelanggan khusus. Kami memberi pelanggan ini diskon 10% tambahan. Kami dapat mencoba mengubah objek order dan meningkatkan ukuran diskon dengan menambahkan 0.1 ke properti diskonnya.

Namun, ini akan mengarah pada kesalahan buruk.


Pesan kesalahan


Perhitungan nilai pesanan salah untuk pelanggan khusus

Dalam situasi yang serupa, yang berikut ini terjadi: properti yang dihitung terus-menerus, dalam loop tak terbatas, "diceritakan kembali". Kami mengubah diskon, properti yang dihitung bereaksi terhadap ini, menghitung ulang total biaya pesanan dan menghasilkan suatu peristiwa. Saat memproses peristiwa ini, diskon meningkat lagi, ini menyebabkan perhitungan ulang properti yang dihitung, dan seterusnya - hingga tak terbatas.

Tampaknya bagi Anda bahwa kesalahan seperti itu tidak dapat dibuat dalam aplikasi nyata. Tapi benarkah begitu? Skrip kami (jika hal seperti ini terjadi dalam aplikasi ini) akan sangat sulit untuk di-debug. Kesalahan seperti itu akan sangat sulit dilacak. Faktanya adalah bahwa untuk kesalahan ini terjadi, perlu bahwa pesanan dibuat oleh pembeli khusus, dan satu pesanan tersebut dapat memiliki 1000 pesanan reguler.

Mengubah Properti Bertumpuk


Terkadang pengembang mungkin tergoda untuk mengedit sesuatu di properti dari properti, yang merupakan objek atau array. Keinginan seperti itu dapat didikte oleh kenyataan bahwa sangat β€œsederhana” untuk melakukannya. Tetapi apakah itu sepadan? Pertimbangkan sebuah contoh.

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

Di sini kita memiliki komponen Product.vue , yang menampilkan nama produk, nilainya, dan jumlah barang yang kita miliki. Komponen juga menampilkan tombol yang memungkinkan pembeli untuk memasukkan barang ke keranjang. Tampaknya akan sangat mudah dan nyaman untuk mengurangi nilai properti product.stock setelah mengklik tombol. Untuk melakukan ini sangat sederhana. Tetapi jika Anda melakukan hal itu, Anda mungkin mengalami beberapa masalah:

  • Kami melakukan perubahan (mutasi) dari properti dan tidak melaporkan apa pun kepada entitas induk.
  • Hal ini dapat menyebabkan perilaku sistem yang tidak terduga, atau, lebih buruk lagi, munculnya kesalahan aneh.
  • Kami memperkenalkan beberapa logika ke dalam komponen product , yang mungkin seharusnya tidak ada di dalamnya.

Bayangkan situasi hipotetis di mana pengembang lain pertama kali menemukan kode kita dan melihat komponen induk.

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

Pemikiran pengembang ini mungkin sebagai berikut: "Rupanya, saya perlu mengurangi product.stock dalam metode addProductToCart " Tetapi jika ini dilakukan, kita akan menemukan kesalahan kecil. Jika sekarang tekan tombol, jumlah barang akan dikurangi bukan oleh 1, tetapi oleh 2.

Bayangkan ini adalah kasus khusus ketika cek semacam itu dibuat hanya untuk barang langka atau sehubungan dengan ketersediaan diskon khusus. Jika kode ini masuk ke produksi, maka semuanya dapat berakhir dengan fakta bahwa pelanggan kami akan, bukannya 1 salinan produk, membeli 2 salinan.

Jika contoh ini terasa tidak meyakinkan bagi Anda, bayangkan skenario lain. Biarkan itu bentuk yang diisi pengguna. Kami meneruskan esensi user ke formulir sebagai properti dan akan mengedit nama dan alamat email pengguna. Kode yang ditunjukkan di bawah ini mungkin tampak "benar."

 //   <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: () => ({})    }  } } 

Sangat mudah untuk memulai dengan user menggunakan arahan v-model . Vue.js memungkinkan ini. Kenapa tidak lakukan itu saja? Pikirkan tentang itu:

  • Bagaimana jika ada persyaratan bahwa Anda perlu menambahkan tombol Batal ke formulir, mengklik yang membatalkan perubahan yang dilakukan?
  • Bagaimana jika panggilan server gagal? Bagaimana cara membatalkan perubahan objek user ?
  • Apakah kami benar-benar ingin menampilkan nama dan alamat email yang diubah di komponen induk sebelum menyimpan perubahan yang sesuai?

Cara sederhana untuk "memperbaiki" masalah mungkin dengan mengkloning objek user sebelum mengirimnya sebagai properti:

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

Meskipun ini mungkin berhasil, kami hanya menghindari masalah, tetapi tidak menyelesaikannya. Komponen UserForm kami harus memiliki status lokal sendiri. Inilah yang bisa kita lakukan.

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

Meskipun kode ini jelas terlihat agak rumit, ini lebih baik daripada versi sebelumnya. Ini memungkinkan Anda untuk menyingkirkan masalah di atas. Kami mengharapkan ( watch ) perubahan pada properti user dan menyalinnya ke dalam data form internal. Hasilnya, formulir sekarang memiliki statusnya sendiri, dan kami mendapatkan fitur berikut:

  • Anda dapat membatalkan perubahan dengan menetapkan kembali formulir: this.form = {...this.user} .
  • Kami memiliki kondisi terisolasi untuk formulir.
  • Tindakan kami tidak memengaruhi komponen induk jika kami tidak membutuhkannya.
  • Kami mengendalikan apa yang terjadi ketika kami mencoba menyimpan perubahan.

Akses langsung ke komponen induk


Jika komponen merujuk ke komponen lain dan melakukan beberapa tindakan di atasnya, ini dapat menyebabkan kontradiksi dan kesalahan, ini dapat mengakibatkan perilaku aplikasi yang aneh dan dalam penampilan komponen terkait di dalamnya.

Pertimbangkan contoh yang sangat sederhana - komponen yang mengimplementasikan menu drop-down. Bayangkan bahwa kita memiliki komponen dropdown (induk) dan komponen dropdown-menu (anak). Ketika pengguna mengklik pada item menu tertentu, kita perlu menutup menu dropdown-menu . Menyembunyikan dan menunjukkan komponen ini dilakukan oleh komponen induk dari dropdown . Lihatlah sebuah contoh.

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

Perhatikan metode selectOption . Meskipun ini jarang terjadi, seseorang mungkin ingin langsung menghubungi $parent . Keinginan ini dapat dijelaskan oleh fakta bahwa itu sangat sederhana untuk dilakukan.

Pada pandangan pertama, sepertinya kode seperti itu berfungsi dengan benar. Tetapi di sini Anda dapat melihat beberapa masalah:

  • Bagaimana jika kita mengubah properti showMenu atau selectedOption ? Menu dropdown tidak akan bisa ditutup dan tidak ada item yang akan dipilih.
  • Bagaimana jika Anda perlu menghidupkan menu dropdown-menu menggunakan semacam transisi?

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

Kode ini, sekali lagi, karena perubahan $parent , tidak akan berfungsi. Komponen dropdown tidak lagi menjadi induk dari menu dropdown-menu . Sekarang induk dropdown-menu adalah komponen transition .

Properti diturunkan hierarki komponen, acara dilewatkan. Kata-kata ini mengandung arti pendekatan yang tepat untuk menyelesaikan masalah kita. Berikut adalah contoh yang dimodifikasi untuk acara.

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

Sekarang, berkat penggunaan acara, komponen anak tidak lagi terikat pada komponen induk. Kita dapat dengan bebas mengubah properti dengan data di komponen induk dan menggunakan transisi animasi. Namun, kami mungkin tidak memikirkan bagaimana kode kami dapat memengaruhi komponen induk. Kami hanya memberi tahu komponen ini tentang apa yang terjadi. Dalam hal ini, komponen dropdown itu sendiri membuat keputusan tentang bagaimana menangani pilihan item menu oleh pengguna dan operasi penutupan menu.

Ringkasan


Kode terpendek tidak selalu paling berhasil. Teknik pengembangan yang melibatkan hasil "sederhana dan cepat" sering cacat. Untuk menggunakan bahasa pemrograman, pustaka atau kerangka kerja dengan benar, Anda perlu kesabaran dan waktu. Ini berlaku untuk Vue.js.

Pembaca yang budiman! Sudahkah Anda mengalami masalah dalam praktik, seperti yang dibahas dalam artikel ini?

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


All Articles