Problèmes de rendu de sept mille éléments sur Vuetify

Avant-propos

Au moment d'écrire ces lignes, je préparais un diplôme et écrivais un projet de diplôme pour les besoins du Poly de Moscou. Ma tâche consiste à transférer la fonctionnalité existante de la table PHP vers quelque chose de moderne avec un tas de vérifications, puis d'ajouter cette fonctionnalité. Moteur - Nuxt, matériel-framework : Vuetify.





Après avoir écrit le code primaire, j'ai, satisfait, j'ai regardé autour de ma table et je suis allé me ​​coucher. Le lendemain, j'ai dû importer plus de 150 projets clients dans ma feuille de calcul. Après l'importation, j'ai été surpris que le navigateur soit gelé. Bon, ça arrive, je viens de rouvrir l'onglet. N'a pas aidé. J'ai d'abord rencontré le problème que je rends trop, à la fois pour le moteur et pour le navigateur lui-même. Je devais commencer à réfléchir.





Premiers essais

Que fait un développeur face à un problème ? Googler. C'est la première chose que j'ai faite. Il s'avère que le problème du rendu lent de la table Vuetify est rencontré avec beaucoup moins d'éléments que moi. Ce qu'ils conseillent :





  • Rendre les éléments pièce par pièce setInterval







  • Définir une condition pour ne pas rendre les éléments tant que le hook de cycle de vie n'est pas déclenché mounted()







  • Utiliser v-lazy



    pour le rendu séquentiel





Virtual Scroller, , . Vuetify Vuetify -_-





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





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





Tableau

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 , "...". , , .





Chargement...
...

( ), . - , : 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 (/ .) , .





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





Et oui : j'ai utilisé le débogueur de performances de Vue et j'ai regardé qui le consommait. Souvent, il y avait littéralement un ou deux composants, et en les remplaçant par un autre avec une logique similaire, le problème n'était pas résolu - la question était dans leur nombre, pas dans leur complexité (sans compter la table Vuetify - il y a beaucoup d'accessoires passés d'un composant à l'autre ).





J'espère que les options que j'ai données pousseront quelqu'un à résoudre son problème, et que quelqu'un apprendra simplement quelque chose de nouveau =). Attendons ensemble une Vue 3 stable avec tout son écosystème, au moins Nuxt 3. Quelque chose promet beaucoup d'améliorations, peut-être que certaines des béquilles de cet article disparaîtront même.








All Articles