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