Componentes de carregamento lento com Blazor - Veja na prática
Componentes de carregamento lento com Blazor - Veja na prática
O que é virtualização?
Vou citar diretamente do site da Microsoft:
"A virtualização é uma técnica para limitar a renderização da interface do usuário apenas às partes que estão visíveis no momento. Por exemplo, a virtualização é útil quando o aplicativo deve renderizar uma longa lista de itens e apenas um subconjunto de itens precisa estar visível a qualquer momento."
Isso tem dois benefícios principais:
- Desenhamos apenas a parte relevante/visível
- Nós apenas carregamos dados para a parte relevante / visível feita corretamente
Virtualizar em ação
Agora, como podemos conseguir isso? Isso é bem simples. Imagine que temos um componente de cartão simples:
<div class="card" style="width: 18rem; height: 300px">
<img src="@ImageUrl" class="card-img-top" alt="">
<div class="card-body">
<p class="card-text">Imagedescription could be here</p>
</div>
</div>
@code {
[Parameter]
public string ImageUrl { get; set; }
protected override void OnInitialized()
{
Console.WriteLine("Called on " + DateTime.Now);
}
}
Agora podemos usar da seguinte forma:
@page "/"
<PageTitle>Index</PageTitle>
<div style="height:500px;overflow-y:scroll">
@foreach (var url in ImageUrls)
{
<ImageCard ImageUrl="@url"></ImageCard>
}
</div>
@code {
private readonly string[] ImageUrls = {
"https://images.unsplash.com/photo-1454496522488-7a8e488e8606?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1176&q=80",
"https://images.unsplash.com/photo-1519681393784-d120267933ba?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1465056836041-7f43ac27dcb5?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1171&q=80",
"https://images.unsplash.com/photo-1483728642387-6c3bdd6c93e5?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1176&q=80",
"https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1549880181-56a44cf4a9a5?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1547093349-65cdba98369a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1480497490787-505ec076689f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1169&q=80",
};
}
O que fazemos é, temos 8 imagens que mostramos em um loop for. Como podemos ver aqui:
Apenas 2 imagens são visíveis por vez. Mas ainda carregamos todas as imagens e renderizamos os cartões mesmo que não sejam visíveis para o nosso usuário. No topo podemos ver a saída do console:
Called on 01/01/2022 20:51:25
Called on 01/01/2022 20:51:25
Called on 01/01/2022 20:51:25
Called on 01/01/2022 20:51:25
Called on 01/01/2022 20:51:25
Called on 01/01/2022 20:51:25
Called on 01/01/2022 20:51:25
Called on 01/01/2022 20:51:25
Podemos ver isso facilmente no inspetor também:
Todas as imagens são carregadas e renderizadas diretamente. Agora vamos verificar como ficaria se virtualizássemos o conteúdo. Nós apenas baixamos e renderizamos as imagens "sob demanda". Antes de mergulharmos no código, vamos dar uma olhada no inspetor novamente:
E a saída do console:
Called on 01/01/2022 21:06:05
Called on 01/01/2022 21:06:05
Called on 01/01/2022 21:06:05
Called on 01/01/2022 21:06:05
Called on 01/01/2022 21:06:05
Called on 01/01/2022 21:06:19
Called on 01/01/2022 21:06:19
Called on 01/01/2022 21:06:19
Called on 01/01/2022 21:06:19
Called on 01/01/2022 21:06:19
Então o que vemos?
- Apenas o primeiro conjunto de cartas está realmente carregado. Uma vez que continuamos a rolar, um novo lote de cartões é carregado e renderizado
- Mas também quando rolamos para cima novamente, o lote que baixamos e exibimos anteriormente deve ser recuperado mais uma vez
< Virtualize> algum código
Agora, como conseguimos isso? Vamos dar uma pequena olhada:
<div style="height:500px;overflow-y:scroll">
<Virtualize Items="@ImageUrls" OverscanCount="1" ItemSize="300">
<ImageCard ImageUrl="@context"></ImageCard>
</Virtualize>
</div>
Agora vamos desembrulhar isso um pouco. Primeiro, substituímos o loop for pelo componente Virtualize. Aqui o link para a documentação oficial. Itens descreve nossa enumeração. Aqui passamos apenas a nossa lista. Agora as próximas duas propriedades não são realmente necessárias e eu as usei apenas para demonstração, mas elas podem ser úteis de qualquer maneira:
- OverscanCount: define o número de itens após e antes da área visível atual que deve ser renderizada. Se você definir esse número muito alto, inicializará desnecessariamente componentes que podem não ser necessários de qualquer maneira. Para baixo e você vê o carregamento visível quando o usuário rola para baixo.
- ItemSize: Simples falando o tamanho por item em pixel. O Blazor por si só pode fazer isso por você, você não precisa necessariamente fornecer isso. O Blazor consegue isso simplesmente renderizando seu componente uma vez.
@context parece muito mágico. Ele contém nosso item. Portanto, em nosso exemplo, ele contém um URL. Você pode dar um nome ao contexto se desejar:
<Virtualize Items="@ImageUrls" Context="@url">
<ImageCard ImageUrl="@url">
</virtualize>
Para casos de uso mais avançados, dê uma olhada aqui
Quando usar
Agora temos uma ideia básica de como substituir um loop for pelo componente Virtualize. O que são casos de uso típicos?
- Renderizando um conjunto de itens de dados em um loop.
- A maioria dos itens não são visíveis devido à rolagem. (imagine algo como rolagem sem fim onde você carrega todos os dados antecipadamente ??)
- Os itens renderizados são do mesmo tamanho.
Agora, por que o último ponto é importante? Blazor tem que estimar (com base em sua janela de visualização) quando carregar novos itens e quando não. Se você muda constantemente os tamanhos de seus itens, isso pode ser muito complicado e levar a problemas.
Provedor de Itens
Em vez de fornecer um IEnumerable ao componente Virtualize, conforme mostrado acima, você também tem a opção de definir um ItemsProvider. A diferença aqui é que você precisa descobrir quais itens carregar, dependendo do estado atual. Para saber onde você está atualmente, o blazor fornece um objeto [ItemsProviderRequest] para o delegado solicitado. Vamos dar uma olhada.
Primeiro o uso:
<Virtualize ItemsProvider="@GetImages" OverscanCount="1" ItemSize="300">
<ImageCard ImageUrl="@context"></ImageCard>
</Virtualize>
Então, em vez da propriedade Items, usamos a propriedade ItemsProvider do componente, o resto permanece o mesmo. Agora para a nova parte, o delegado:
private async ValueTask<ItemsProviderResult<string>> GetImages(ItemsProviderRequest request)
{
await Task.Yield(); // Just to make it async
var numImages = Math.Min(request.Count, ImageUrls.Length - request.StartIndex);
var urls = ImageUrls.Skip(request.StartIndex).Take(numImages);
return new ItemsProviderResult<string>(urls, ImageUrls.Length);
}
Estamos apenas carregando um lote de imagens do nosso variedade e devolvendo-o ao provedor.
Observação: não defina tanto Items quanto ItemsProvider, pois o componente lançará uma InvalidOperationException.
Espaço reservado
Agora leve a última parte um pouco mais adiante. Carregamos imagens de um ItemsProvider. Normalmente isso leva algum tempo. Para exibir algo em vez disso, podemos aproveitar a propriedade Placeholder. Observação: isso só funcionará com ItemsProvider e não com itens no momento em que estou escrevendo essa postagem no blog (.NET 6).
<Virtualize Items="@GetImages" OverscanCount="1" ItemSize="300">
<ItemContent>
<DelayComponent></DelayComponent>
</ItemContent>
<Placeholder>
<h2>Loading...</h2>
</Placeholder>
</Virtualize>
O caso de uso típico seria que você recuperasse alguns dados de um repositório dentro de seu ItemsProviderDelegate.
Conclusão
A virtualização é uma boa maneira de reduzir a carga e a pressão do seu aplicativo. Partimos do básico para os cenários mais avançados. se você tiver alguma dúvida me avise. A contribuição e o feedback são sempre bem-vindos!