Problemas de renderização de sete mil elementos no Vuetify

Prefácio

No momento em que este artigo foi escrito, eu estava me preparando para um diploma e escrevendo um projeto de diploma para as necessidades da Poli de Moscou. Minha tarefa é transferir a funcionalidade existente da tabela PHP para algo moderno com um monte de verificações e, em seguida, adicionar essa funcionalidade. Engine - Nuxt, material-framework: Vuetify.





Depois de escrever o código primário, eu, satisfeito, olhei em volta da minha mesa e fui para a cama. No dia seguinte, tive que importar mais de 150 projetos de clientes para minha planilha. Depois de importar, fiquei surpreso ao ver que o navegador estava travado. Bem, acontece, eu acabei de reabrir a guia. Não ajudou. Eu encontrei pela primeira vez o problema de que estou renderizando muito, tanto para o mecanismo quanto para o próprio navegador. Eu tive que começar a pensar.





Primeiras tentativas

O que um desenvolvedor faz quando enfrenta um problema? Pesquisando no Google. Esta foi a primeira coisa que fiz. Acontece que o problema de renderização lenta da tabela Vuetify é encontrado com muito menos elementos do que eu. O que eles aconselham:





  • Renderizar elementos peça por peça setInterval







  • Defina uma condição para não renderizar os elementos até que o gancho do ciclo de vida seja acionado mounted()







  • Use v-lazy



    para renderização sequencial





Virtual Scroller, , . Vuetify Vuetify -_-





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





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





Mesa

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





Carregando...
...

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





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





E sim: usei o depurador de desempenho do Vue e observei quem o estava consumindo. Muitas vezes havia literalmente um ou dois componentes, e substituindo-os por algum outro com lógica semelhante, o problema não foi resolvido - o problema estava em seu número, não na complexidade (sem contar a tabela Vuetify - há muitos adereços passados ​​de componente para componente )





Espero que as opções que dei levem alguém a resolver seu problema, e alguém simplesmente aprenda algo novo =). Vamos esperar juntos por um Vue 3 estável com todo o seu ecossistema, pelo menos Nuxt 3. Algo promete muitas melhorias, talvez algumas das muletas deste artigo até desapareçam.








All Articles