Componentes de carregamento lento com Blazor - Veja na prática

Imagem de capa 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:

  1. Desenhamos apenas a parte relevante/visível
  2. 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!