Partilhar via


Virtualização de componentes do ASP.NET Core Razor

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Este artigo explica como usar a virtualização de componentes em aplicativos ASP.NET Core Blazor.

Virtualização

Melhore o desempenho percebido da renderização de componentes usando o suporte de virtualização interno da estrutura de Blazor com o componente Virtualize<TItem>. 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 é necessário para estar visível a qualquer momento.

Use o componente Virtualize<TItem> quando:

  • Renderização de um conjunto de itens de dados em um loop.
  • A maioria dos itens não é visível devido à rolagem.
  • Os itens renderizados são do mesmo tamanho.

Quando o usuário rola para um ponto arbitrário na lista de itens do componente Virtualize<TItem>, o componente calcula os itens visíveis a serem exibidos. Itens invisíveis não são renderizados.

Sem virtualização, uma lista típica pode usar um loop de foreach C# para renderizar cada item em uma lista. No exemplo a seguir:

  • allFlights é uma coleção de voos de avião.
  • O componente FlightSummary exibe detalhes sobre cada voo.
  • O atributo da diretiva @key preserva a relação de cada componente FlightSummary com o seu voo renderizado pela FlightIddo voo.
<div style="height:500px;overflow-y:scroll">
    @foreach (var flight in allFlights)
    {
        <FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
    }
</div>

Se a coleção contiver milhares de voos, a renderização dos voos levará muito tempo e os usuários experimentarão um atraso notável na interface do usuário. A maioria dos voos fica fora da altura do elemento <div>, por isso a maioria não é visível.

Em vez de renderizar toda a lista de voos de uma só vez, substitua o loop de foreach no exemplo anterior pelo componente Virtualize<TItem>:

  • Especifique allFlights como uma fonte de item fixa para Virtualize<TItem>.Items. Apenas os vôos atualmente visíveis são renderizados pelo componente Virtualize<TItem>.

    Se uma coleção não genérica fornecer os itens, por exemplo, uma coleção de DataRow, siga as orientações na seção relativa ao delegado do fornecedor de itens para fornecer os itens.

  • Especifique um contexto para cada voo com o parâmetro Context. No exemplo a seguir, usa-se flight como contexto, proporcionando acesso aos membros de cada voo.

<div style="height:500px;overflow-y:scroll">
    <Virtualize Items="allFlights" Context="flight">
        <FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
    </Virtualize>
</div>

Se um contexto não for especificado com o parâmetro Context, use o valor de context no modelo de conteúdo do item para acessar os membros de cada voo:

<div style="height:500px;overflow-y:scroll">
    <Virtualize Items="allFlights">
        <FlightSummary @key="context.FlightId" Details="@context.Summary" />
    </Virtualize>
</div>

O componente Virtualize<TItem>:

  • Calcula o número de itens a serem renderizados com base na altura do contêiner e no tamanho dos itens renderizados.
  • Recalcula e re-renderiza os itens à medida que o utilizador rola.
  • Somente busca a porção de registros de uma API externa que correspondam à região visível no momento, incluindo a área de pré-visualização, quando ItemsProvider é usado em vez de Items (consulte a seção Delegado do Provedor de Itens).

O conteúdo do item para o componente Virtualize<TItem> pode incluir:

  • HTML simples e código Razor, como mostra o exemplo anterior.
  • Um ou mais componentes Razor.
  • Uma mistura de HTML/Razor e Razor componentes.

Delegado do provedor de itens

Se você não quiser carregar todos os itens na memória ou se a coleção não for um ICollection<T>genérico, você pode especificar um método de delegação do provedor de itens para o parâmetro Virtualize<TItem>.ItemsProvider do componente que recupera de forma assíncrona os itens solicitados sob demanda. No exemplo a seguir, o método LoadEmployees fornece os itens para o componente Virtualize<TItem>:

<Virtualize Context="employee" ItemsProvider="LoadEmployees">
    <p>
        @employee.FirstName @employee.LastName has the 
        job title of @employee.JobTitle.
    </p>
</Virtualize>

O provedor de itens recebe um ItemsProviderRequest, que especifica o número necessário de itens começando em um índice inicial específico. Em seguida, o provedor de itens recupera os itens solicitados de um banco de dados ou outro serviço e os retorna como um ItemsProviderResult<TItem> juntamente com uma contagem do total de itens. O provedor de itens pode optar por recuperar os itens com cada solicitação ou armazená-los em cache para que estejam prontamente disponíveis.

Um componente Virtualize<TItem> só pode aceitar uma fonte de item dos seus parâmetros, por isso, não tente usar simultaneamente um provedor de itens e atribuir uma coleção a Items. Se ambos forem atribuídos, uma InvalidOperationException será gerada quando os parâmetros do componente forem definidos em tempo de execução.

O exemplo a seguir carrega funcionários de um EmployeeService (não mostrado). O campo totalEmployees normalmente seria atribuído chamando um método no mesmo serviço (por exemplo, EmployeesService.GetEmployeesCountAsync) em outro lugar, como durante a inicialização do componente.

private async ValueTask<ItemsProviderResult<Employee>> LoadEmployees(
    ItemsProviderRequest request)
{
    var numEmployees = Math.Min(request.Count, totalEmployees - request.StartIndex);
    var employees = await EmployeesService.GetEmployeesAsync(request.StartIndex, 
        numEmployees, request.CancellationToken);

    return new ItemsProviderResult<Employee>(employees, totalEmployees);
}

No exemplo a seguir, uma coleção de DataRow é uma coleção não genérica, portanto, um delegado do provedor de itens é usado para virtualização:

<Virtualize Context="row" ItemsProvider="GetRows">
    ...
</Virtualize>

@code{
    ...

    private ValueTask<ItemsProviderResult<DataRow>> GetRows(ItemsProviderRequest request) => 
        new(new ItemsProviderResult<DataRow>(
            dataTable.Rows.OfType<DataRow>().Skip(request.StartIndex).Take(request.Count),
            dataTable.Rows.Count));
}

Virtualize<TItem>.RefreshDataAsync instrui o componente a requisitar novamente os dados do seu ItemsProvider. Isso é útil quando os dados externos são alterados. Normalmente, não há necessidade de ligar para RefreshDataAsync ao usar Items.

RefreshDataAsync atualiza os dados de um componente Virtualize<TItem> sem causar uma nova renderização. Se RefreshDataAsync for invocado a partir de um manipulador de eventos Blazor ou método de ciclo de vida do componente, o acionamento de uma renderização não será necessário porque uma renderização será acionada automaticamente no final do manipulador de eventos ou do método do ciclo de vida. Se RefreshDataAsync for acionado separadamente de uma tarefa ou evento em segundo plano, como no seguinte método delegado ForecastUpdated, chame StateHasChanged para atualizar a IU no final da tarefa ou evento em segundo plano.

<Virtualize ... @ref="virtualizeComponent">
    ...
</Virtualize>

...

private Virtualize<FetchData>? virtualizeComponent;

protected override void OnInitialized()
{
    WeatherForecastSource.ForecastUpdated += async () => 
    {
        await InvokeAsync(async () =>
        {
            await virtualizeComponent?.RefreshDataAsync();
            StateHasChanged();
        });
    });
}

No exemplo anterior:

  • RefreshDataAsync é chamado primeiro para obter novos dados para o componente Virtualize<TItem>.
  • StateHasChanged é chamado para renderizar novamente o componente.

Espaço reservado

Como a solicitação de itens de uma fonte de dados remota pode levar algum tempo, você tem a opção de renderizar um espaço reservado com conteúdo de item:

  • Use um Placeholder (<Placeholder>...</Placeholder>) para exibir o conteúdo até que os dados do item estejam disponíveis.
  • Use Virtualize<TItem>.ItemContent para definir o modelo de item para a lista.
<Virtualize Context="employee" ItemsProvider="LoadEmployees">
    <ItemContent>
        <p>
            @employee.FirstName @employee.LastName has the 
            job title of @employee.JobTitle.
        </p>
    </ItemContent>
    <Placeholder>
        <p>
            Loading&hellip;
        </p>
    </Placeholder>
</Virtualize>

Conteúdo vazio

Use o parâmetro EmptyContent para disponibilizar conteúdo depois que o componente tiver sido carregado e Items estiver vazio ou ItemsProviderResult<TItem>.TotalItemCount seja zero.

EmptyContent.razor:

@page "/empty-content"

<PageTitle>Empty Content</PageTitle>

<h1>Empty Content Example</h1>

<Virtualize Items="stringList">
    <ItemContent>
        <p>
            @context
        </p>
    </ItemContent>
    <EmptyContent>
        <p>
            There are no strings to display.
        </p>
    </EmptyContent>
</Virtualize>

@code {
    private List<string>? stringList;

    protected override void OnInitialized() => stringList ??= [];
}
@page "/empty-content"

<PageTitle>Empty Content</PageTitle>

<h1>Empty Content Example</h1>

<Virtualize Items="stringList">
    <ItemContent>
        <p>
            @context
        </p>
    </ItemContent>
    <EmptyContent>
        <p>
            There are no strings to display.
        </p>
    </EmptyContent>
</Virtualize>

@code {
    private List<string>? stringList;

    protected override void OnInitialized() => stringList ??= [];
}

Altere o método lambda de OnInitialized para ver as cadeias de caracteres de exibição do componente:

protected override void OnInitialized() =>
    stringList ??= [ "Here's a string!", "Here's another string!" ];

Tamanho do item

A altura de cada item em pixels pode ser definida com Virtualize<TItem>.ItemSize (padrão: 50). O exemplo a seguir altera a altura de cada item do padrão de 50 pixels para 25 pixels:

<Virtualize Context="employee" Items="employees" ItemSize="25">
    ...
</Virtualize>

O componente Virtualize<TItem> mede o tamanho de renderização (altura) de itens individuais após a renderização inicial ocorrer. Use ItemSize para fornecer um tamanho exato do item com antecedência para ajudar com o desempenho de renderização inicial preciso e garantir a posição de rolagem correta para recarregamentos de página. Se o ItemSize padrão fizer com que alguns itens sejam renderizados fora da exibição visível no momento, uma segunda renderização será acionada. Para manter corretamente a posição de rolagem do navegador em uma lista virtualizada, a renderização inicial deve estar correta. Caso contrário, os usuários podem visualizar os itens errados.

Contagem de overscan

Virtualize<TItem>.OverscanCount determina quantos itens adicionais são renderizados antes e depois da região visível. Essa configuração ajuda a reduzir a frequência de renderização durante a rolagem. No entanto, valores mais altos resultam em mais elementos renderizados na página (padrão: 3). O exemplo a seguir altera a contagem de overscan do padrão de três itens para quatro itens:

<Virtualize Context="employee" Items="employees" OverscanCount="4">
    ...
</Virtualize>

Mudanças de Estado

Ao fazer alterações nos itens renderizados pelo componente Virtualize<TItem>, chame StateHasChanged para agendar a reavaliação e renderização do componente novamente. Para obter mais informações, consulte Razorde renderização de componentes .

Suporte de rolagem do teclado

Para permitir que os usuários rolem conteúdo virtualizado usando o teclado, verifique se os elementos virtualizados ou o próprio contêiner de rolagem são focados. Se você não executar essa etapa, a rolagem do teclado não funcionará em navegadores baseados no Chromium.

Por exemplo, você pode usar um atributo tabindex no contêiner de rolagem:

<div style="height:500px; overflow-y:scroll" tabindex="-1">
    <Virtualize Items="allFlights">
        <div class="flight-info">...</div>
    </Virtualize>
</div>

Para saber mais sobre o significado de tabindex valor -1, 0ou outros valores, consulte tabindex.

Estilos avançados e deteção de rolagem

O componente Virtualize<TItem> é projetado apenas para suportar mecanismos de layout de elementos específicos. Para entender quais layouts de elementos funcionam corretamente, a seguir explicamos como o Virtualize deteta quais elementos devem ser visíveis para exibição no local correto.

Se o seu código-fonte tiver a seguinte aparência:

<div style="height:500px; overflow-y:scroll" tabindex="-1">
    <Virtualize Items="allFlights" ItemSize="100">
        <div class="flight-info">Flight @context.Id</div>
    </Virtualize>
</div>

No tempo de execução, o componente Virtualize<TItem> renderiza uma estrutura DOM semelhante à seguinte:

<div style="height:500px; overflow-y:scroll" tabindex="-1">
    <div style="height:1100px"></div>
    <div class="flight-info">Flight 12</div>
    <div class="flight-info">Flight 13</div>
    <div class="flight-info">Flight 14</div>
    <div class="flight-info">Flight 15</div>
    <div class="flight-info">Flight 16</div>
    <div style="height:3400px"></div>
</div>

O número real de linhas renderizadas e o tamanho dos espaçadores variam de acordo com o estilo e o tamanho da coleção Items. No entanto, observe que há elementos espaçadores div injetados antes e depois do seu conteúdo. Estas servem dois propósitos:

  • Para fornecer um deslocamento antes e depois do conteúdo, garantindo que os itens atualmente visíveis apareçam na localização correta dentro do intervalo de rolagem, e que esse intervalo de rolagem represente o tamanho total de todo o conteúdo.
  • Para detetar quando o usuário está rolando além do intervalo visível atual, o que significa que conteúdo diferente deve ser renderizado.

Observação

Para saber como controlar a marca de elemento HTML do espaçador, consulte a seção Controlar o nome da marca do elemento do espaçador mais adiante neste artigo.

Os elementos do espaçador usam internamente um Observador de Interseção para receber notificação quando estão a tornar-se visíveis. Virtualize depende de receber esses eventos.

Virtualize funciona nas seguintes condições:

  • Todos os itens de conteúdo renderizados, incluindo o conteúdo de espaço reservado , têm altura idêntica. Isso torna possível calcular qual conteúdo corresponde a uma determinada posição de rolagem sem primeiro buscar cada item de dados e renderizar os dados em um elemento DOM.

  • Tanto os espaçadores como as linhas de conteúdo são renderizados numa única coluna vertical, com cada item a ocupar toda a largura horizontal. Em casos de uso típicos, Virtualize funciona com elementos div. Se você estiver usando CSS para criar um layout mais avançado, tenha em mente os seguintes requisitos:

    • O estilo do contentor de rolagem requer um display com qualquer um dos seguintes valores:
      • block (o padrão para um div).
      • table-row-group (o padrão para um tbody).
      • flex com flex-direction definido como column. Certifique-se de que os filhos imediatos do componente Virtualize<TItem> não diminuam sob regras flexíveis. Por exemplo, adicione .mycontainer > div { flex-shrink: 0 }.
    • O estilo da linha de conteúdo requer um display com um dos seguintes valores:
      • block (o padrão para um div).
      • table-row (o padrão para um tr).
    • Não use CSS para interferir no layout dos elementos do espaçador. Os elementos do espaçador têm um valor display de block, exceto se o pai for um grupo de linhas da tabela, neste caso, o valor padrão é table-row. Não tente influenciar a largura ou a altura dos elementos espaçadores, incluindo ao dar-lhes uma borda ou pseudoelementos como content.

Qualquer abordagem que impeça os espaçadores e os elementos de conteúdo de aparecer como uma única pilha vertical, ou fazer com que os itens de conteúdo variem em altura, impede o funcionamento correto do componente Virtualize<TItem>.

Virtualização em nível raiz

O componente Virtualize<TItem> suporta o uso do próprio documento como a raiz de rolagem, como uma alternativa para ter algum outro elemento com overflow-y: scroll. No exemplo a seguir, os elementos <html> ou <body> são estilizados em um componente com overflow-y: scroll:

<HeadContent>
    <style>
        html, body { overflow-y: scroll }
    </style>
</HeadContent>

O componente Virtualize<TItem> suporta o uso do próprio documento como a raiz de rolagem, como uma alternativa para ter algum outro elemento com overflow-y: scroll. Ao usar o documento como raiz de rolagem, evite estilizar os elementos <html> ou <body> com overflow-y: scroll, fazendo com que o observador de interseção trate a altura total rolável da página como a região visível, em vez de apenas o visor da janela.

Você pode reproduzir esse problema criando uma grande lista virtualizada (por exemplo, 100.000 itens) e tentar usar o documento como a raiz de rolagem com html { overflow-y: scroll } nos estilos CSS da página. Embora possa funcionar corretamente às vezes, o navegador tenta renderizar todos os 100.000 itens pelo menos uma vez no início da renderização, o que pode causar um bloqueio da guia do navegador.

Para contornar esse problema antes do lançamento do .NET 7, evite estilizar elementos <html>/<body> com overflow-y: scroll ou adote uma abordagem alternativa. No exemplo a seguir, a altura do elemento <html> é definida como pouco mais de 100% da altura da janela de visualização:

<HeadContent>
    <style>
        html { min-height: calc(100vh + 0.3px) }
    </style>
</HeadContent>

O componente Virtualize<TItem> suporta o uso do próprio documento como a raiz de rolagem, como uma alternativa para ter algum outro elemento com overflow-y: scroll. Ao usar o documento como a raiz de rolagem, evite estilizar os elementos <html> ou <body> com overflow-y: scroll, pois isso faz com que a altura total rolável da página seja tratada como a região visível, em vez de apenas a janela de visualização.

Você pode reproduzir esse problema criando uma grande lista virtualizada (por exemplo, 100.000 itens) e tentar usar o documento como a raiz de rolagem com html { overflow-y: scroll } nos estilos CSS da página. Embora possa funcionar corretamente às vezes, o navegador tenta renderizar todos os 100.000 itens pelo menos uma vez no início da renderização, o que pode causar um bloqueio da guia do navegador.

Para contornar esse problema antes do lançamento do .NET 7, evite estilizar elementos <html>/<body> com overflow-y: scroll ou adote uma abordagem alternativa. No exemplo a seguir, a altura do elemento <html> é definida como pouco mais de 100% da altura da janela de visualização:

<style>
    html { min-height: calc(100vh + 0.3px) }
</style>

Controlar o nome da etiqueta do elemento espaçador

Se o componente Virtualize<TItem> for colocado dentro de um elemento que exige um nome de etiqueta filho específico, SpacerElement permite-lhe obter ou definir o nome da etiqueta do espaçador de virtualização. O valor padrão é div. Para o exemplo a seguir, o componente Virtualize<TItem> é renderizado dentro de um elemento de corpo de tabela (tbody), assim o elemento filho apropriado para uma linha numa tabela (tr) é definido como o espaçador.

VirtualizedTable.razor:

@page "/virtualized-table"

<PageTitle>Virtualized Table</PageTitle>

<HeadContent>
    <style>
        html, body {
            overflow-y: scroll
        }
    </style>
</HeadContent>

<h1>Virtualized Table Example</h1>

<table id="virtualized-table">
    <thead style="position: sticky; top: 0; background-color: silver">
        <tr>
            <th>Item</th>
            <th>Another column</th>
        </tr>
    </thead>
    <tbody>
        <Virtualize Items="fixedItems" ItemSize="30" SpacerElement="tr">
            <tr @key="context" style="height: 30px;" id="row-@context">
                <td>Item @context</td>
                <td>Another value</td>
            </tr>
        </Virtualize>
    </tbody>
</table>

@code {
    private List<int> fixedItems = Enumerable.Range(0, 1000).ToList();
}

No exemplo anterior, a raiz do documento é usada como o contêiner de rolagem, de modo que os elementos html e body são estilizados com overflow-y: scroll. Para obter mais informações, consulte os seguintes recursos: