Em 2020, as estruturas de front-end se tornaram melhores, mais eficientes e mais rápidas. Mas, mesmo com isso em mente, renderizar grandes listas sem congelar o navegador ainda pode ser uma tarefa assustadora, mesmo para as estruturas mais rápidas existentes.Este é um daqueles casos em que "a estrutura é rápida e o código é lento". Existem muitas abordagens que permitem exibir um grande número de elementos sem bloquear a interação do usuário com a página da web. O autor do artigo, cuja tradução estamos publicando hoje, deseja explorar os métodos existentes para exibir grandes listas em páginas da web e falar sobre suas áreas de aplicação.
Embora este material seja destinado ao Angular, o que está sendo discutido aqui se aplica a outras estruturas e projetos que são escritos em JavaScript puro. Em particular, as seguintes abordagens para renderizar grandes listas serão consideradas aqui:- Rolagem virtual (usando Angular CDK).
- Renderização manual.
- Renderização progressiva.
Rolagem virtual
A rolagem virtual é talvez a maneira mais eficiente de trabalhar com listas grandes. É verdade que ele tem algumas desvantagens. A rolagem virtual, graças ao Angular CDK e outros plugins, é muito fácil de implementar em qualquer componente.A idéia por trás da rolagem virtual é simples, mas nem sempre fácil de implementar. A linha inferior aqui é que temos um contêiner e uma lista de elementos. Um elemento é renderizado apenas se estiver dentro da área visível do contêiner.Usaremos o módulo scrolling
do Angular CDK, projetado para organizar a rolagem virtual. Para fazer isso, primeiro você precisa instalar o CDK:npm i @angular/cdk
Então você precisa importar o módulo:import { ScrollingModule } from '@angular/cdk/scrolling';
@NgModule({
...
imports: [ ScrollingModule, ...]
})
export class AppModule {}
Depois disso, nos componentes você pode usar cdk-virtual-scroll-viewport
:<cdk-virtual-scroll-viewport itemSize="50">
<div *cdkVirtualFor="let item of items">
{{ item }}
</div>
</cdk-virtual-scroll-viewport>
Aqui está um exemplo de projeto que usa essa abordagem para organizar a rolagem virtual.Como você pode ver, os mecanismos angulares padrão permitem, sem muita dificuldade, obter resultados impressionantes. O componente no exemplo renderiza muitos milhares de elementos sem problemas.Se a rolagem virtual é tão boa e fácil de implementar, surge a questão de por que estudar outras maneiras de renderizar grandes listas. Eu também estava interessado nesta questão. Como se viu, esse estado de coisas tem vários motivos:- , , . , . ,
Autocomplete
( ). , , , , . — . - — , .
- Existem alguns problemas de acessibilidade e facilidade de uso do conteúdo de uma lista com rolagem virtual. Os elementos ocultos não são renderizados - isso significa que eles não estarão disponíveis para os leitores de tela e que não poderão ser encontrados na página usando mecanismos padrão do navegador.
A rolagem virtual é ideal em várias situações (desde que funcione):- Nesse caso, se você deseja exibir listas cujo tamanho não é conhecido antecipadamente ou aquelas que podem ser enormes (de acordo com uma estimativa aproximada, listas que incluem mais de 5 mil elementos, mas isso depende muito da complexidade de cada elemento).
- Caso você precise organizar a rolagem sem fim.
Renderização manual
Uma das maneiras de trabalhar com listas que tentei acelerar a saída de um grande conjunto de elementos é usar a renderização manual usando a API Angular *ngFor
.Temos um modelo simples que usa um loop organizado usando a diretiva *ngFor
<tr
*ngFor="let item of data; trackBy: trackById; let isEven = even; let isOdd = odd"
class="h-12"
[class.bg-gray-400]="isEven"
[class.bg-gray-500]="isOdd"
>
<td>
<span class="py-2 px-4">{{ item.id }}</span>
</td>
<td>
<span>{{ item.label }}</span>
</td>
<td>
<a>
<button class="py-2 px-4 rounded (click)="remove(item)">x</button>
</a>
</td>
</tr>
Para medir o desempenho de renderização de 10.000 elementos simples, usei um benchmark baseado em js-frameworks-benchmark .Primeiro, examinei o desempenho de uma lista, que usa um loop regular para a saída *ngFor
. Como resultado, verificou-se que a execução do código (Scripting) levou 1099 ms., A renderização (Rendering) levou 1553 ms. E o desenho (Pintura) - 3 ms.Um estudo do desempenho de uma lista, que usa * ngFor para produzir, ositens da Lista podem ser renderizados manualmente usando a API Angular:<tbody>
<ng-container #itemsContainer></ng-container>
</tbody>
<ng-template #item let-item="item" let-isEven="isEven">
<tr class="h-12 "
[class.bg-gray-400]="isEven"
[class.bg-gray-500]="!isEven"
>
<td>
<span class="py-2 px-4">{{ item.id }}</span>
</td>
<td>
<span>{{ item.label }}</span>
</td>
<td>
<a>
<button class="py-2 px-4 rounded" (click)="remove(item)">x</button>
</a>
</td>
</tr>
</ng-template>
Vamos falar sobre como o código do controlador mudou.Declaramos um modelo e um contêiner:@ViewChild('itemsContainer', { read: ViewContainerRef }) container: ViewContainerRef;
@ViewChild('item', { read: TemplateRef }) template: TemplateRef<any>;
Ao gerar dados, nós os renderizamos usando o método de createEmbeddedView
entidade ViewContainerRef
:private buildData(length: number) {
const start = this.data.length;
const end = start + length;
for (let n = start; n <= end; n++) {
this.container.createEmbeddedView(this.template, {
item: {
id: n,
label: Math.random()
},
isEven: n % 2 === 0
});
}
}
Como resultado, os indicadores que caracterizam o desempenho da lista foram ligeiramente aprimorados. Ou seja, foram necessários 734 ms para executar o código, 1443 ms para renderizar e 2 ms para desenhar.Pesquisando o desempenho de uma lista que usa a API Angular para gerarTrue, na prática, isso significa que a lista ainda é extremamente lenta. Quando você clica no botão apropriado, o navegador "congela" por alguns segundos. Se isso aparecesse na produção, os usuários definitivamente não gostariam.Aqui está o que parece (aqui eu, usando o mouse, imito o indicador de carregamento).Lentidão na listaAgora, para melhorar a renderização manual da lista, tentaremos aplicar a tecnologia de renderização progressiva.Renderização progressiva
A idéia de renderização progressiva é simples. Consiste em renderizar um subconjunto de elementos progressivamente, adiando a renderização de outros elementos no loop de eventos. Isso permite que o navegador exiba todos os elementos sem "diminuir a velocidade".O código abaixo que implementa a renderização progressiva da lista é muito direto:- Primeiro, usando isso
setInterval
, estabelecemos uma chamada regular a cada 10 ms para a função que renderiza 500 elementos quando chamada. - Depois de deduzidos todos os elementos, que determinamos com base na análise do índice do elemento atual, paramos a chamada de função regular e interrompemos o ciclo.
private buildData(length: number) {
const ITEMS_RENDERED_AT_ONCE = 500;
const INTERVAL_IN_MS = 10;
let currentIndex = 0;
const interval = setInterval(() => {
const nextIndex = currentIndex + ITEMS_RENDERED_AT_ONCE;
for (let n = currentIndex; n <= nextIndex ; n++) {
if (n >= length) {
clearInterval(interval);
break;
}
const context = {
item: {
id: n,
label: Math.random()
},
isEven: n % 2 === 0
};
this.container.createEmbeddedView(this.template, context);
}
currentIndex += ITEMS_RENDERED_AT_ONCE;
}, INTERVAL_IN_MS);
Observe que o número de elementos exibidos por chamada para a função que está sendo transferida setInterval
, bem como a frequência da chamada para essa função, dependem completamente das circunstâncias. Por exemplo, se os elementos da lista exibida forem muito complexos, a saída de 500 desses elementos de uma só vez será inaceitavelmente lenta.Ao medir o desempenho desta solução, obtive resultados que parecem piores do que aqueles que recebi anteriormente. Execução de código - 907 ms., Renderização - 2555 ms., Desenho - 16 ms.Pesquisando o desempenho de uma lista, cuja saída usa renderização progressiva,mas o usuário, trabalhando com essa lista, experimentará uma experiência muito mais agradável do que antes. Embora o tempo necessário para renderizar a lista tenha aumentado, o usuário não notará. De uma só vez, renderizamos 500 elementos. Nesse caso, a renderização é realizada fora dos limites do contêiner.Aqui, alguns problemas podem surgir devido ao fato de o contêiner mudar de tamanho durante a renderização ou a posição da rolagem do conteúdo ser alterada. Se isso acontecer, você terá que lidar com problemas semelhantes.Aqui está a aparência de trabalhar com essa lista.A lista é rápidaSumário
Técnicas de renderização manual e progressiva para listas grandes certamente são úteis em algumas situações. Eu os usei nos casos em que a rolagem virtual, por algum motivo, não era adequada para mim.Dado o exposto, podemos dizer que, na maioria das vezes, a rolagem virtual, construída com base em uma boa biblioteca, como o Angular CDK, é a melhor maneira de exibir grandes listas. Mas se a rolagem virtual não puder ser usada por algum motivo, o desenvolvedor terá outras possibilidades.Queridos leitores! Como você produz grandes listas em seus projetos da web?