Vuetify で 7,000 要素をレンダリングする際の問題

序文

この記事を書いている時点で、私は卒業証書の準備をしており、モスクワ ポリーの必要に応じて卒業証書プロジェクトを書いていました。私のタスクは、既存の機能を PHP テーブルから一連のチェックを備えた最新の機能に転送し、この機能を追加することです。エンジン - Nuxt、マテリアル フレームワーク: Vuetify。





主要なコードを書いた後、私は満足してテーブルを見回して寝ました。翌日、150 以上の顧客プロジェクトをスプレッドシートにインポートする必要がありました。インポートした後、ブラウザがフリーズしていたことに驚きました。たまたま、タブを再び開いただけです。役に立たなかった。最初に、エンジンとブラウザ自体の両方でレンダリングが多すぎるという問題に遭遇しました。考え始めなければなりませんでした。





最初の試み

問題に直面したとき、開発者は何をしますか? ググる。これが私が最初にしたことでした。結局のところ、Vuetify テーブルのレンダリングが遅いという問題は、私が持っているよりもはるかに少ない要素で発生します。彼らがアドバイスすること:





  • 要素を 1 つずつレンダリングします。 setInterval







  • ライフサイクル フックがトリガーされるまで要素をレンダリングしないように条件を設定します mounted()







  • v-lazy



    シーケンシャル レンダリングに使用





同時に、スクロールしながら要素を描画したり、前の要素をレンダリングしたりできる Virtual Scroller コンポーネントを使用する提案がありました。しかし、この Vuetify コンポーネントは Vuetify テーブルでは機能しません -_-





Vuetify 3 (約 6 か月後にリリース) でパフォーマンスが 50% 向上するという「喜び」を読んで、解決策を試し始めました。条件付きの1000番目の要素でレンダリングが遅れ始め、7000までにすべてが再びハングしたため、部分的な要素のレンダリングは何も与えませんでした。要素を Mounted にレンダリングしても何も得られず、すべてがフリーズしましたが、ページがロードされた後 (えー、えっと?)。v-lazy のレンダリングは高速ですが、14,000 個のコンポーネント (Vuetify と Vue からの移行) のレンダリングも悲しい作業です。





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





テーブル

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





読み込んでいます...
...

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





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





はい、私は Vue のパフォーマンス デバッガーを使用して、誰がそれを使用しているかを観察しました。多くの場合、文字通り1つまたは2つのコンポーネントがあり、それらを同様のロジックを持つ他のコンポーネントと交換すると、問題は解決されませんでした-問題は複雑さではなく、その数にありました(Vuetifyテーブルは数えません-コンポーネントからコンポーネントに渡される多くの小道具があります) )。





私が与えたオプションが誰かに彼の問題を解決するように促し、誰かが何か新しいことを学ぶことを願っています=)。安定した Vue 3 とそのエコシステム全体、少なくとも Nuxt 3 を一緒に待ちましょう。多くの改善が約束されており、この記事の松葉杖の一部が消えることさえあるでしょう。








All Articles