Artikel ini memiliki sembilan tips tentang cara meningkatkan kinerja aplikasi Vue Anda, meningkatkan kecepatan tampilan dan mengurangi ukuran bundel.
Bekerja dengan komponen
Komponen fungsional
Misalkan kita memiliki komponen kecil yang sederhana. Yang dia lakukan adalah menampilkan tag tertentu tergantung pada nilai yang diteruskan:
<template> <div> <div v-if="value"></div> <section v-else></section> </div> </template> <script> export default { props: ['value'] } </script>
Komponen ini dapat dioptimalkan dengan menambahkan atribut
fungsional . Komponen fungsional mengkompilasi menjadi fungsi sederhana dan tidak memiliki status lokal. Karena ini, kinerjanya jauh lebih tinggi:
<template functional> <div> <div v-if="props.value"></div> <section v-else></section> </div> </template> <script> export default { props: ['value'] } </script>
Contoh komponen fungsional apa yang mungkin terlihat di Vue v3.0Pemisahan menjadi komponen anak
Bayangkan sebuah komponen untuk ditampilkan yang perlu Anda lakukan beberapa tugas kompleks:
<template> <div :style="{ opacity: number / 300 }"> <div>{{ heavy() }}</div> </div> </template> <script> export default { props: ['number'], methods: { heavy () { } } } </script>
Masalahnya di sini adalah bahwa Vue akan mengeksekusi metode heavy () setiap kali komponen dirender ulang, yaitu, setiap kali nilai props berubah.
Kita dapat dengan mudah mengoptimalkan komponen seperti itu jika kita memisahkan metode berat menjadi komponen anak:
<template> <div :style="{ opacity: number / 300 }"> <ChildComp/> </div> </template> <script> export default { props: ['number'], components: { ChildComp: { methods: { heavy () { } }, render (h) { return h('div', this.heavy()) } } } } </script>
Mengapa Vue secara otomatis melewatkan rendering komponen di mana data dependen tidak berubah. Jadi, ketika alat peraga diubah dalam komponen induk, anak akan digunakan kembali dan metode berat () tidak akan dimulai kembali.
Perhatikan bahwa ini hanya masuk akal jika komponen anak tidak memiliki ketergantungan data pada komponen induk. Kalau tidak, anak akan dibuat kembali bersama dengan orang tua, dan kemudian optimasi ini tidak masuk akal.
Cache Lokal Getter
Komponen berikut memiliki beberapa jenis properti yang dihitung berdasarkan properti yang dihitung kedua:
<template> <div :style="{ opacity: start / 300 }">{{ result }}</div> </template> <script> export default { props: ['start'], computed: { base () { return 42 }, result () { let result = this.start for (let i = 0; i < 1000; i++) { result += this.base } return result } } } </script>
Yang penting di sini adalah bahwa properti dasar disebut dalam satu lingkaran, yang mengarah pada komplikasi. Setiap kali mengakses data reaktif, Vue menjalankan beberapa logika untuk menentukan bagaimana dan data apa yang Anda akses untuk membangun dependensi dan sebagainya. Overhead kecil ini diringkas jika ada banyak panggilan, seperti dalam contoh kita.
Untuk memperbaikinya, cukup mengakses basis sekali dan menyimpan nilai ke variabel lokal:
<template> <div :style="{ opacity: start / 300 }">{{ result }}</div> </template> <script> export default { props: ['start'], computed: { base () { return 42 }, result () { const base = this.base </script>
Menggunakan kembali DOM dengan v-show
Lihatlah contoh berikut:
<template functional> <div> <div v-if="props.value"> <Heavy :n="10000"/> </div> <section v-else> <Heavy :n="10000"/> </section> </div> </template>
Di sini kita memiliki komponen pembungkus yang menggunakan v-jika dan v-lain untuk mengganti beberapa komponen anak.
Sangat penting untuk memahami bagaimana v-if bekerja. Setiap kali, ketika keadaan diaktifkan, satu komponen anak akan sepenuhnya hancur (kait dihancurkan () dipanggil, semua node akan dihapus dari DOM), dan yang kedua akan sepenuhnya dibuat dan dipasang lagi. Dan jika komponen-komponen ini “berat”, maka Anda akan memahami antarmuka pada saat beralih.
Solusi yang lebih produktif - gunakan v-show
<template functional> <div> <div v-show="props.value"> <Heavy :n="10000"/> </div> <section v-show="!props.value"> <Heavy :n="10000"/> </section> </div> </template>
Dalam hal ini,
kedua komponen anak akan dibuat dan dipasang segera dan ada secara bersamaan. Dengan demikian, Vue tidak perlu menghancurkan dan membuat komponen saat berpindah. Semua yang dia lakukan adalah menyembunyikan satu dan menunjukkan yang kedua dengan CSS. Jadi beralih status akan jauh lebih cepat, tetapi Anda harus memahami bahwa ini akan menyebabkan biaya memori tinggi.
Gunakan <keep-alive>
Jadi, komponen sederhana adalah komponen pembungkus di atas
router .
<template> <div id="app"> <router-view/> </div> </template>
Masalahnya mirip dengan contoh sebelumnya - semua komponen di router Anda akan dibuat, dipasang dan dihancurkan selama transisi antar rute.
Dan solusinya di sini mirip - untuk memberitahu Vue untuk tidak menghancurkan tetapi untuk men-cache dan menggunakan kembali komponen. Anda dapat melakukan ini menggunakan komponen
<keep-alive> built-in khusus:
<template> <div id="app"> <keep-alive> <router-view/> </keep-alive> </div> </template>
Optimalisasi ini akan mengarah pada peningkatan konsumsi memori, karena Vue akan mendukung lebih banyak komponen "hidup". Karena itu, Anda tidak boleh menggunakan pendekatan ini tanpa berpikir untuk semua rute, terutama jika Anda memiliki banyak rute. Anda dapat menggunakan atribut include dan exclude untuk menetapkan aturan rute mana yang harus di-cache dan yang tidak:
<template> <div id="app"> <keep-alive include="home,some-popular-page"> <router-view/> </keep-alive> </div> </template>
Render yang ditangguhkan
Contoh berikut adalah komponen yang memiliki beberapa komponen anak yang sangat berat:
<template> <div> <h3>I'm an heavy page</h3> <Heavy v-for="n in 10" :key="n"/> <Heavy class="super-heavy" :n="9999999"/> </div> </template>
Masalahnya adalah bahwa pemrosesan komponen adalah operasi sinkron di utas utama. Dan dalam contoh ini, browser tidak akan menampilkan apa pun sampai pemrosesan semua komponen, baik induk maupun anak, selesai. Dan jika dibutuhkan banyak waktu untuk memproses anak perusahaan, maka Anda akan mendapatkan keterlambatan di antarmuka atau layar kosong untuk beberapa waktu.
Anda dapat memperbaiki situasi dengan menunda rendering komponen anak:
<template> <div> <h3>I'm an heavy page</h3> <template v-if="defer(2)"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="defer(3)" class="super-heavy" :n="9999999"/> </div> </template> <script> import Defer from '@/mixins/Defer' export default { mixins: [ Defer() ] } </script>
Di sini, fungsi penundaan (n) mengembalikan frame n palsu. setelah itu selalu mengembalikan true. Ini digunakan untuk menunda pemrosesan bagian dari template oleh beberapa frame, sehingga memberi browser kemampuan untuk menggambar antarmuka.
Bagaimana cara kerjanya? Seperti yang saya tulis di atas, jika kondisi dalam direktif v-if salah, maka Vue sepenuhnya mengabaikan bagian dari template.
Pada frame pertama dari animasi kita dapatkan:
<template> <div> <h3>I'm an heavy page</h3> <template v-if="false"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="false" class="super-heavy" :n="9999999"/> </div> </template>
Vue hanya memasang tajuk, dan browser menampilkannya dan meminta bingkai kedua. Pada titik ini, penundaan (2) akan mulai mengembalikan nilai true
<template> <div> <h3>I'm an heavy page</h3> <template v-if="true"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="false" class="super-heavy" :n="9999999"/> </div> </template>
Vue akan membuat 10 komponen anak dan memasangnya. Browser akan menampilkannya dan meminta frame berikutnya, di mana defer (3) akan mengembalikan true.
Jadi, kami membuat komponen berat yang diproses secara bertahap, memberikan browser kemampuan untuk menampilkan bagian templat yang sudah dipasang, yang akan sangat meningkatkan UX karena antarmuka tidak akan terlihat seperti hang.
Kode Penunda Mixer:
export default function (count = 10) { return { data () { return { displayPriority: 0 } }, mounted () { this.runDisplayPriority() }, methods: { runDisplayPriority () { const step = () => { requestAnimationFrame(() => { this.displayPriority++ if (this.displayPriority < count) { step() } }) } step() }, defer (priority) { return this.displayPriority >= priority } } } }
Pemuatan komponen yang malas
Sekarang mari kita bicara tentang mengimpor komponen anak:
import Heavy from 'Heavy.js' export default { components: { Heavy } }
Dalam impor tradisional, komponen anak langsung dimuat, saat browser mencapai pernyataan impor. Atau, jika Anda menggunakan kolektor, komponen anak Anda akan dimasukkan dalam bundel umum.
Namun, jika komponen anak Anda sangat besar, masuk akal untuk memuatnya secara tidak sinkron.
Sangat mudah dilakukan:
const Heavy = () => import('Heavy.js') export default { components: { Heavy } }
Itu saja yang Anda butuhkan . Vue dapat bekerja dengan memuat komponen yang malas di luar kotak. Dia akan mengunduh komponennya sendiri saat dibutuhkan dan menampilkannya segera setelah siap. Anda dapat menggunakan pendekatan ini untuk memuat malas di mana saja.
Jika Anda menggunakan kolektor, maka segala sesuatu tentang Heavy.js akan dikeluarkan dalam file terpisah. Dengan demikian, Anda akan mengurangi bobot file saat unduhan awal dan meningkatkan kecepatan tampilan.
Tampilan Pemuatan Malas
Pemuatan malas sangat berguna dalam kasus komponen yang digunakan untuk rute. Karena Anda tidak perlu mengunduh semua komponen untuk rute secara bersamaan, saya sarankan Anda
selalu menggunakan pendekatan ini:
const Home = () => import('Home.js') const About = () => import('About.js') const Contacts = () => import('Contacts.js') new VueRouter({ routes: [ { path: '/', component: Home }, { path: '/about', component: About }, { path: '/contacts', component: Contacts }, ] })
Masing-masing komponen ini akan dimuat hanya ketika pengguna pertama kali meminta rute yang ditentukan. Dan tidak sebelumnya.
Komponen dinamis
Demikian juga, Anda dapat dengan mudah menggunakan pemuatan malas dengan komponen dinamis:
<template> <div> <component :is="componentToShow"/> </div> </template> <script> export default { props: ['value'], computed: { componentToShow() { if (this.value) { return () => import('TrueComponent.js') } return () => import('FalseComponent.js') } } } </script>
Sekali lagi, setiap komponen hanya akan memuat ketika dibutuhkan.
Bekerja dengan Vuex
Hindari komitmen besar
Bayangkan Anda perlu menyimpan sejumlah besar data dalam penyimpanan:
fetchItems ({ commit }, { items }) { commit('clearItems') commit('addItems', items)
Masalahnya adalah ini. Semua komit adalah operasi yang sinkron. Ini berarti bahwa pemrosesan array besar akan memblokir antarmuka Anda selama durasi pekerjaan.
Untuk mengatasi masalah ini, Anda dapat membagi array menjadi beberapa bagian dan menambahkannya satu per satu, memberikan waktu kepada browser untuk menggambar bingkai baru:
fetchItems ({ commit }, { items, splitCount }) { commit('clearItems') const queue = new JobQueue() splitArray(items, splitCount).forEach( chunk => queue.addJob(done => {
Semakin kecil pihak yang Anda tambahkan data ke penyimpanan, semakin halus antarmuka Anda akan tetap ada dan semakin lama total waktu untuk menyelesaikan tugas.
Selain itu, dengan pendekatan ini, Anda memiliki kemampuan untuk menampilkan indikator unduhan untuk pengguna. Yang juga akan secara signifikan meningkatkan pengalamannya dengan aplikasi.
Matikan reaktivitas di tempat yang tidak diperlukan
Dan contoh terakhir untuk hari ini. Kami memiliki tugas yang serupa: kami menambahkan array objek yang sangat besar ke penyimpanan dengan sekelompok tingkat bersarang:
const data = items.map( item => ({ id: uid++, data: item,
Faktanya adalah bahwa Vue akan secara rekursif melintasi semua bidang bersarang dan membuatnya reaktif. Jika Anda memiliki banyak data, ini bisa mahal, tetapi yang jauh lebih penting - tidak perlu.
Jika aplikasi Anda dibuat sedemikian rupa sehingga hanya bergantung pada objek tingkat atas dan tidak merujuk ke data reaktif di beberapa tingkat di bawah ini, maka Anda dapat menonaktifkan reaktivitas, sehingga menghemat Vue dari banyak pekerjaan yang tidak perlu:
const data = items.map( item => optimizeItem(item) ) function optimizeItem (item) { const itemData = { id: uid++, vote: 0 } Object.defineProperty(itemData, 'data', {