Masalah saat merender tujuh ribu elemen di Vuetify

Kata pengantar

Pada saat penulisan ini, saya sedang mempersiapkan diploma dan menulis proyek diploma untuk kebutuhan Poli Moskow. Tugas saya adalah mentransfer fungsionalitas yang ada dari tabel PHP ke sesuatu yang modern dengan banyak cek, dan kemudian menambahkan fungsionalitas ini. Engine - Nuxt, material-framework: Vuetify.





Setelah menulis kode utama, saya, puas, melihat sekeliling ke meja saya dan pergi tidur. Hari berikutnya, saya harus mengimpor 150+ proyek pelanggan ke dalam spreadsheet saya. Setelah mengimpor, saya terkejut bahwa browser dibekukan. Nah, itu terjadi, saya baru saja membuka kembali tab. Tidak membantu. Saya pertama kali mengalami masalah yang saya render terlalu banyak, baik untuk mesin dan untuk browser itu sendiri. Saya harus mulai berpikir.





Upaya pertama

Apa yang dilakukan pengembang ketika menghadapi masalah? googling. Ini adalah hal pertama yang saya lakukan. Ternyata, masalah rendering tabel Vuetify yang lambat dihadapi dengan elemen yang jauh lebih sedikit daripada yang saya miliki. Apa yang mereka sarankan:





  • Render elemen sepotong demi sepotong setInterval







  • Tetapkan kondisi untuk tidak merender elemen hingga kait siklus hidup dipicu mounted()







  • Gunakan v-lazy



    untuk rendering berurutan





Virtual Scroller, , . Vuetify Vuetify -_-





"" , Vuetify 3 ( ~) 50%, . , , . mounted , , , (, ?). v-lazy , 14 (Vuetify Transition Vue) .





, , . , , . . , StackOverflow, , , .





Meja

1. Intersection Observer

, . v-lazy , 14 . Vuetify Virtual Scroller Vuetify Data Table - . , . , ? Intersection Observer.



Internet Explorer , .





: v-intersect



Vuetify. 7 =(. , .





mounted() {
  //     overflow: auto
  //    : 10%
	this.observer = new IntersectionObserver(this.handleObserve, { root: this.$refs.table as any, threshold: 0.1 });
	//   observe    ?  
	for (const element of Array.from(document.querySelectorAll('#intersectionElement'))) {
		this.observer.observe(element);
	}
},
      
      



handleObserve:





async handleObserve(entries: IntersectionObserverEntry[]) {
		const parsedEntries = entries.map(entry => {
			const target = entry.target as HTMLElement;
  		//  data-
			const project = +(target.dataset.projectId || '0');
			const speciality = +(target.dataset.specialityId || '0');

			return {
          isIntersecting: entry.isIntersecting,
          project,
          speciality,
			};
    });

		//   
    this.$set(this, 'observing', [
      //  
      ...parsedEntries.filter(x => x.isIntersecting && !this.observing.some(y => y.project === x.project && y.speciality === x.speciality)),
      // 
      ...this.observing.filter(entry => !parsedEntries.some(x => !x.isIntersecting && x.project === entry.project && x.speciality === entry.speciality)),
     ]);

		//    
     Array.from(document.querySelectorAll('#intersectionElement')).forEach((target) => this.observer?.unobserve(target));
     // Vuetify 
		 await this.$nextTick();
		 // 300,     ,    
     await new Promise((resolve) => setTimeout(resolve, 500));
     //  
     Array.from(document.querySelectorAll('#intersectionElement'))
          .forEach((target) => this.observer?.observe(target));
},
      
      



, 7 , Intersection Observer. observing, projectId specialityId, , . - v-if - . !





 <template #[`item.speciality-${speciality.id}`]="{item, headers}" v-for="speciality in getSpecialities()">
	<div id="intersectionElement" :data-project-id="item.id" :data-speciality-id="speciality.id">
		<ranking-projects-table-item
			v-if="observing.some(
					x => x.project === item.id && x.speciality === speciality.id
			)"
			:speciality="speciality"
			:project="item"
		/>
		<template v-else>
			...
		</template>
	</div>
</template>

      
      



v-once. $forceUpdate



. , Vuetify , .





<v-data-table 
	v-bind="getTableSettings()" 
  v-once 
  :items="projects" 
  @update:expanded="$forceUpdate()">
      
      



:

















. 7 , "...". , , .





Memuat...
...

( ), . - , : Vuetify Data Table , .





?





2.

, , , . , . .





:





  1. Vuetify





  2. ,





  3. - , ""





  4. Intersection Observer , , (300 )





Virtual Scroller. Vuetify, ? display: grid



? - .





Virtual Scroller? . Grid'? . CSS- CSS:





<div class="ranking-table" :style="{
    '--projects-count': getSettings().projectsCount,
    '--specialities-count': getSettings().specialitiesCount,
    '--first-column-width': `${getSettings().firstColumnWidth}px`,
    '--others-columns-width': `${getSettings().othersColumnsWidth}px`,
    '--cell-width': `${getSettings().firstColumnWidth + getSettings().othersColumnsWidth * getSettings().specialitiesCount}px`,
    '--item-height': `${getSettings().itemHeight}px`
  }">
      
      







display: grid;
grid-template-columns:
	var(--first-column-width)
	repeat(var(--specialities-count), var(--others-columns-width));
      
      



. ! , , Virtual Scroller ( ), , -





.ranking-table_v2__scroll::v-deep {
	.v-virtual-scroll {
		&__container, &__item, .ranking-table_v2__project {
    	width: var(--cell-width);
		}
	}
}
      
      



: <style>



scoped



, , : - App.vue, , v-deep.





: Virtual Scroller, , . : Expandable Items , . , , , Vuetify, , . , :





<v-virtual-scroll 
  class="ranking-table_v2__scroll" 
  :height="getSettings().commonHeight"
	:item-height="getSettings().itemHeight"
	:items="projects">
		<template #default="{item}">
			<div class="ranking-table_v2__project" :key="item.id">
				<!-- ... -->
      
      



: , , 6 ( ), 6 + . 50. 300 . , 300 .





v-lazy: . 14 , 600 . ( ) v-lazy. , , .





 <v-lazy class="ranking-table_v2__item ranking-table_v2__item--speciality"
	v-for="(speciality, index) in specialities"
	:key="speciality.id">
   <!--     -->
</v-lazy>
      
      



, :





:









  • /





  • v-once $forceUpdate









:





  • (expand),





  • ,





  • / ( )





  • , , , Scroll





  • , window.innerHeight CSS VirtualScroll





, , UX .





2 Vuetify. , . , . , , Vuetify (/ .) , .





? . , , . , , , - , - .





Dan ya: Saya menggunakan debugger kinerja Vue dan melihat siapa yang menggunakannya. Seringkali ada satu atau dua komponen, dan menggantinya dengan yang lain dengan logika yang sama, masalahnya tidak terpecahkan - masalahnya ada pada jumlah mereka, bukan kompleksitas (tidak termasuk tabel Vuetify - ada banyak alat peraga yang diteruskan dari komponen ke komponen ).





Saya berharap opsi yang saya berikan akan mendorong seseorang untuk menyelesaikan masalahnya, dan seseorang akan belajar sesuatu yang baru =). Mari kita tunggu bersama Vue 3 yang stabil dengan seluruh ekosistemnya, setidaknya Nuxt 3. Sesuatu menjanjikan banyak perbaikan, mungkin beberapa penopang dari artikel ini bahkan akan hilang.








All Articles