Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão do .NET 10 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão do .NET 10 deste artigo.
Otimize a velocidade de renderização para minimizar a carga de trabalho de renderização e melhorar a capacidade de resposta da interface do usuário, o que pode produzir uma melhoria de dez vezes ou mais na velocidade de renderização da interface do usuário.
Evitar a renderização desnecessária das subárvores de componente
Você pode remover a maior parte do custo de renderização de um componente pai ignorando a nova renderização das subárvores de componente filho, quando ocorre um evento. Você só deve se preocupar em ignorar a nova renderização de subárvores que são particularmente caras de renderizar e estão causando o atraso na interface do usuário.
Em runtime, os componentes existem em uma hierarquia. Um componente raiz (o primeiro componente carregado) tem componentes filho. Por sua vez, os filhos da raiz têm seus próprios componentes filho e assim por diante. Quando ocorre um evento, como um usuário selecionando um botão, o processo a seguir determina quais componentes gerar novamente:
- O evento é expedido para o componente que renderiza o manipulador do evento. Depois de executar o manipulador de eventos, o componente é renderizado novamente.
- Quando um componente é renderizado novamente, ele fornece uma nova cópia dos valores de parâmetro para cada um de seus componentes filho.
- Depois que um novo conjunto de valores de parâmetro é recebido, Blazor decide se deseja rerender o componente. Os componentes são renderizados novamente se
ShouldRenderretornartrue, o que é o comportamento padrão, a menos que seja substituído, e os valores dos parâmetros possam ter mudado, por exemplo, se forem objetos mutáveis.
As duas últimas etapas da sequência anterior continuam recursivamente abaixo da hierarquia de componentes. Em muitos casos, toda a subárvore é renderizada novamente. Eventos direcionados a componentes de alto nível podem causar uma nova renderização cara, pois cada componente abaixo do componente de alto nível deve ser renderizado novamente.
Para evitar a recursão de renderização em uma subárvore específica, use uma das seguintes abordagens:
- Verifique se os parâmetros de componente filho são de tipos imutáveis específicos†, como
string,int,booleDateTime. A lógica embutida para detectar alterações ignora automaticamente a re-renderização caso os valores dos parâmetros imutáveis não tenham mudado. Se você renderizar um componente filho com<Customer CustomerId="item.CustomerId" />, ondeCustomerIdé um tipoint, o componenteCustomernão será renderizado novamente, a menos queitem.CustomerIdseja alterado. - Sobrescrever
ShouldRender, retornandofalse:- Quando os parâmetros são tipos não primitivos ou tipos imutáveis sem suporte†, como tipos complexos de modelo personalizado ou valores RenderFragment, e os valores de parâmetro não foram alterados,
- Se estiver criando um componente somente de interface do usuário que não seja alterado após a renderização inicial, independentemente das alterações no valor do parâmetro.
†Para obter mais informações, consulte a lógica de detecção de alterações na Blazororigem de referência (ChangeDetection.cs).
Observação
Os links de documentação para o código fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).
O exemplo da ferramenta de pesquisa de voo de companhia aérea a seguir usa campos privados para acompanhar as informações necessárias para detectar alterações. O identificador do voo de entrada anterior (prevInboundFlightId) e o identificador do voo de saída anterior (prevOutboundFlightId) rastreiam informações para a próxima possível atualização do componente. Se qualquer um dos identificadores de voo mudar quando os parâmetros do componente estiverem definidos em OnParametersSet, o componente será rerenderizado porque shouldRender está definido para true. Se shouldRender for avaliado como false depois de verificar os identificadores da versão de pré-lançamento, uma nova renderização cara será evitada:
@code {
private int prevInboundFlightId = 0;
private int prevOutboundFlightId = 0;
private bool shouldRender;
[Parameter]
public FlightInfo? InboundFlight { get; set; }
[Parameter]
public FlightInfo? OutboundFlight { get; set; }
protected override void OnParametersSet()
{
shouldRender = InboundFlight?.FlightId != prevInboundFlightId
|| OutboundFlight?.FlightId != prevOutboundFlightId;
prevInboundFlightId = InboundFlight?.FlightId ?? 0;
prevOutboundFlightId = OutboundFlight?.FlightId ?? 0;
}
protected override bool ShouldRender() => shouldRender;
}
Um manipulador de eventos também pode definir shouldRender como true. Para a maioria dos componentes, determinar a nova renderização no nível dos manipuladores de eventos individuais geralmente não é necessário.
Para obter mais informações, consulte os seguintes recursos:
- Ciclo de vida do componente Razor do ASP.NET Core
- ShouldRender
- Renderização do componente ASP.NET CoreRazor
Virtualização
Ao renderizar grandes quantidades de interface do usuário em um loop, por exemplo, uma lista ou grade com milhares de entradas, a grande quantidade de operações de renderização pode levar a um atraso na renderização da interface do usuário. Dado que o usuário só pode ver um pequeno número de elementos de uma só vez sem rolar, muitas vezes é um desperdício gastar tempo renderizando elementos que não estão visíveis no momento.
Blazor fornece o componente Virtualize<TItem> para criar a aparência e os comportamentos de rolagem de uma lista de tamanho arbitrário, renderizando apenas os itens de lista que estão dentro da área de visualização atual. Por exemplo, um componente pode renderizar uma lista com 100.000 entradas, mas pagar apenas o custo de renderização de 20 itens visíveis.
Para obter mais informações, confira Virtualização de componentes Razor do ASP.NET Core.
Criar componentes leves e otimizados
A maioria dos componentes Razor não exige esforços agressivos de otimização, pois a maioria dos componentes não se repete na interface do usuário e não é renderizada novamente em alta frequência. Por exemplo, os componentes roteáveis com uma diretiva @page e componentes usados para renderizar partes de alto nível da interface do usuário, como caixas de diálogo ou formulários, provavelmente aparecem apenas um de cada vez e só são renderizados novamente em resposta a um gesto do usuário. Esses componentes geralmente não criam uma carga de trabalho de renderização alta, portanto, você pode usar livremente qualquer combinação de recursos de estrutura sem muita preocupação com o desempenho de renderização.
No entanto, há cenários comuns em que os componentes são repetidos em escala e geralmente resultam em um desempenho ruim da interface do usuário:
- Formulários grandes e aninhados com centenas de elementos individuais, como campos de entrada ou etiquetas.
- Grades com centenas de linhas ou milhares de células.
- Gráficos de dispersão com milhões de pontos de dados.
Se cada elemento, célula ou ponto de dados for modelado como uma instância de componente separada, geralmente há tantos que o desempenho de renderização se torna crítico. Esta seção fornece conselhos sobre como tornar esses componentes leves para que a interface do usuário permaneça rápida e responsiva.
Evitar milhares de instâncias de componente
Cada componente é uma ilha separada que pode renderizar independentemente dos pais e filhos. Ao escolher como dividir a interface do usuário em uma hierarquia de componentes, você está assumindo o controle sobre a granularidade da renderização da interface do usuário. Isso pode resultar em um bom ou ruim desempenho.
Ao dividir a interface do usuário em componentes separados, você pode ter partes menores da nova renderização da interface do usuário, quando ocorrem eventos. Em uma tabela com muitas linhas que têm um botão em cada linha, você pode ter apenas uma única linha renderizada usando um componente filho, em vez de toda a página ou tabela. No entanto, cada componente requer memória adicional e sobrecarga de CPU para lidar com seu estado independente e ciclo de vida de renderização.
Em um teste realizado pelos engenheiros da unidade de produto do ASP.NET Core, uma sobrecarga de renderização de cerca de 0,06 ms por instância de componente foi vista em um Blazor WebAssembly aplicativo. O aplicativo de teste renderiza um componente simples que aceita três parâmetros. Internamente, a sobrecarga deve-se, em grande parte, à recuperação do estado de cada componente a partir de dicionários e à passagem e recebimento de parâmetros. Por multiplicação, você pode ver que a adição de 2.000 instâncias de componentes adicionais adicionaria 0,12 segundos ao tempo de renderização e a interface do usuário começará a ficar lenta para os usuários.
É possível tornar os componentes mais leves para que você possa ter mais deles. No entanto, uma técnica mais poderosa geralmente é evitar ter tantos componentes para renderizar. As seções a seguir descrevem duas abordagens que você pode adotar.
Para obter mais informações sobre o gerenciamento de memória, consulte Gerenciar memória em aplicativos ASP.NET Core implantados no lado do servidorBlazor.
Inserir componentes filhos em seus componentes pais: considere a seguinte parte de um componente pai que renderiza componentes filhos em um loop:
<div class="chat">
@foreach (var message in messages)
{
<ChatMessageDisplay Message="message" />
}
</div>
ChatMessageDisplay.razor:
<div class="chat-message">
<span class="author">@Message.Author</span>
<span class="text">@Message.Text</span>
</div>
@code {
[Parameter]
public ChatMessage? Message { get; set; }
}
O exemplo anterior terá um bom desempenho se milhares de mensagens não forem mostradas de uma só vez. Para mostrar milhares de mensagens de uma só vez, não considere o componente ChatMessageDisplay separado. Em vez disso, embuta o componente filho no pai. A abordagem a seguir evita a sobrecarga por componente de renderizar tantos componentes filho às custas de perder a capacidade de renderizar novamente a marcação de cada componente filho de forma independente:
<div class="chat">
@foreach (var message in messages)
{
<div class="chat-message">
<span class="author">@message.Author</span>
<span class="text">@message.Text</span>
</div>
}
</div>
Defina reutilizável RenderFragments no código: você pode estar separando componentes filhos apenas como uma forma de reutilizar a lógica de renderização. Se esse for o caso, você poderá criar uma lógica de renderização reutilizável sem implementar componentes adicionais. Em qualquer bloco de @code de um componente, defina um RenderFragment. Renderize o fragmento de qualquer local quantas vezes for necessário:
@RenderWelcomeInfo
<p>Render the welcome content a second time:</p>
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!</p>;
}
Para tornar RenderTreeBuilder o código reutilizável em vários componentes, declare o RenderFragmentpublic e static:
public static RenderFragment SayHello = @<h1>Hello!</h1>;
SayHello no exemplo anterior pode ser invocado de um componente não relacionado. Essa técnica é útil para criar bibliotecas de snippets de marcação reutilizáveis que são renderizados sem sobrecarga por componente.
RenderFragment os delegados podem aceitar parâmetros. O seguinte componente passa a mensagem (message) para o RenderFragment delegado:
<div class="chat">
@foreach (var message in messages)
{
@ChatMessageDisplay(message)
}
</div>
@code {
private RenderFragment<ChatMessage> ChatMessageDisplay = message =>
@<div class="chat-message">
<span class="author">@message.Author</span>
<span class="text">@message.Text</span>
</div>;
}
A abordagem anterior reutiliza a lógica de renderização sem sobrecarga por componente. No entanto, a abordagem não permite atualizar a subárvore da interface do usuário de forma independente, nem tem a capacidade de ignorar a renderização da subárvore da interface do usuário quando o pai é renderizado, pois não há limite de componente. A atribuição a um delegado de RenderFragment só tem suporte em arquivos de componente Razor (.razor).
Para um campo, método ou propriedade não estático que não pode ser referenciado por um inicializador de campo, como TitleTemplate no exemplo a seguir, use uma propriedade em vez de um campo para:RenderFragment
protected RenderFragment DisplayTitle =>
@<div>
@TitleTemplate
</div>;
Não receba muitos parâmetros
Se um componente se repetir com muita frequência, por exemplo, centenas ou milhares de vezes, a sobrecarga de passar e receber cada parâmetro será acumulada.
É raro que muitos parâmetros restrinjam severamente o desempenho, mas pode ser um fator. Para um TableCell componente que renderiza 4.000 vezes em uma grade, cada parâmetro passado para o componente adiciona cerca de 15 ms ao custo total de renderização. Passar dez parâmetros requer cerca de 150 ms e causa um atraso de renderização da interface do usuário.
Para reduzir a carga do parâmetro, agrupe vários parâmetros em uma classe personalizada. Por exemplo, um componente de célula de tabela pode aceitar um objeto comum. No exemplo a seguir, Data é diferente para cada célula, mas Options é comum em todas as instâncias de célula:
@typeparam TItem
...
@code {
[Parameter]
public TItem? Data { get; set; }
[Parameter]
public GridOptions? Options { get; set; }
}
No entanto, tenha em mente que agrupar parâmetros primitivos em uma classe nem sempre é uma vantagem. Embora possa reduzir a contagem de parâmetros, ela também afeta o comportamento de detecção e renderização de alterações. A passagem de parâmetros não primitivos sempre dispara uma renderização novamente, porque Blazor não é possível saber se os objetos arbitrários têm um estado mutável internamente, enquanto a passagem de parâmetros primitivos só dispara uma renderização se seus valores realmente foram alterados.
Além disso, considere que pode ser uma melhoria não ter um componente de célula de tabela, conforme mostrado no exemplo anterior, e, em vez disso, embutir sua lógica no componente pai.
Observação
Quando várias abordagens estão disponíveis para melhorar o desempenho, o benchmark das abordagens geralmente é necessário para determinar qual abordagem produz os melhores resultados.
Para obter mais informações sobre parâmetros de tipo genérico (@typeparam), consulte os seguintes recursos:
- Razor Referência de sintaxe para ASP.NET Core
- Componentes Razor do ASP.NET Core
- Componentes com modelo Blazor no ASP.NET Core
Garantir que os parâmetros em cascata sejam corrigidos
O CascadingValue componente tem um parâmetro opcional IsFixed :
- Se
IsFixedforfalse(o padrão), cada destinatário do valor em cascata configurará uma assinatura para receber as notificações de alteração. Cada[CascadingParameter]é substancialmente mais caro do que um[Parameter]normal devido ao monitoramento da assinatura. - Se
IsFixedétrue(por exemplo,<CascadingValue Value="someValue" IsFixed="true">), os destinatários recebem o valor inicial, mas não configuram uma assinatura para receber atualizações. Cada[CascadingParameter]um é leve e não mais caro do que um normal[Parameter].
A configuração IsFixed para true melhora o desempenho se houver um grande número de outros componentes que recebem o valor em cascata. Sempre que possível, defina IsFixed como true em valores em cascata. Você pode definir IsFixedtrue quando o valor fornecido não é alterado ao longo do tempo.
Quando um componente passa this como um valor em cascata, IsFixed também pode ser definido como true, porque this nunca é alterado durante o ciclo de vida do componente:
<CascadingValue Value="this" IsFixed="true">
<SomeOtherComponents>
</CascadingValue>
Para obter mais informações, confira Valores em cascata e parâmetros Blazor do ASP.NET Core.
Evitar o splatting de atributos com CaptureUnmatchedValues
Os componentes podem optar por receber valores de parâmetro "incompatíveis" usando o CaptureUnmatchedValues sinalizador:
<div @attributes="OtherAttributes">...</div>
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? OtherAttributes { get; set; }
}
Essa abordagem permite passar atributos adicionais arbitrários para o elemento. No entanto, essa abordagem é cara porque o renderizador deve:
- Compare todos os parâmetros fornecidos com os parâmetros conhecidos para construir um dicionário.
- Acompanhe como várias cópias do mesmo atributo substituem umas às outras.
Use CaptureUnmatchedValues onde o desempenho de renderização de componentes não é crítico, como componentes que não são repetidos com frequência. Para componentes que são renderizados em escala, como cada item em uma lista grande ou nas células de uma grade, tente evitar o splatting de atributos.
Para obter mais informações, consulte Distribuição de atributos Blazor do ASP.NET Core e parâmetros arbitrários.
Implementar SetParametersAsync manualmente
Uma fonte significativa de sobrecarga de renderização por componente é gravar valores de parâmetro de entrada em propriedades [Parameter]. O renderizador usa reflexão para gravar os valores de parâmetro, o que pode levar a um desempenho em escala ruim.
Em alguns casos extremos, talvez você queira evitar a reflexão e implementar sua própria lógica de configuração de parâmetro manualmente. Isso pode ser aplicável quando:
- Um componente é renderizado com muita frequência, por exemplo, quando há centenas ou milhares de cópias do componente na interface do usuário.
- Um componente aceita muitos parâmetros.
- Você descobre que a sobrecarga de recebimento de parâmetros tem um impacto observável na capacidade de resposta da interface do usuário.
Em casos extremos, você pode substituir o método virtual SetParametersAsync do componente e implementar sua própria lógica específica do componente. O exemplo a seguir evita deliberadamente pesquisas de dicionário:
@code {
[Parameter]
public int MessageId { get; set; }
[Parameter]
public string? Text { get; set; }
[Parameter]
public EventCallback<string> TextChanged { get; set; }
[Parameter]
public Theme CurrentTheme { get; set; }
public override Task SetParametersAsync(ParameterView parameters)
{
foreach (var parameter in parameters)
{
switch (parameter.Name)
{
case nameof(MessageId):
MessageId = (int)parameter.Value;
break;
case nameof(Text):
Text = (string)parameter.Value;
break;
case nameof(TextChanged):
TextChanged = (EventCallback<string>)parameter.Value;
break;
case nameof(CurrentTheme):
CurrentTheme = (Theme)parameter.Value;
break;
default:
throw new ArgumentException($"Unknown parameter: {parameter.Name}");
}
}
return base.SetParametersAsync(ParameterView.Empty);
}
}
No código anterior, retornar a classe SetParametersAsync base executa o método de ciclo de vida normal sem atribuir parâmetros novamente.
Como você pode ver no código anterior, substituir SetParametersAsync e fornecer lógica personalizada é complicado e trabalhoso, portanto, geralmente não recomendamos adotar essa abordagem. Em casos extremos, a abordagem pode produzir uma pequena melhoria de renderização, normalmente abaixo de ~10%, mesmo em instâncias de mais de 10.000 componentes ao utilizar .NET 10. Os ganhos potenciais são menores em versões posteriores porque a atribuição de parâmetro baseada em reflexão é otimizada. Considere essa abordagem apenas nos cenários extremos listados anteriormente nesta seção e faça primeiro um teste de desempenho — a economia geralmente é superada por outros custos, como, por exemplo, o transporte de SignalRdiferenças (edições no DOM) para o Servidor Interativo.
Como você pode ver no código anterior, substituir SetParametersAsync e fornecer lógica personalizada é complicado e trabalhoso, portanto, geralmente não recomendamos adotar essa abordagem. Em casos extremos, a abordagem pode melhorar o desempenho de renderização em 20-25%, mas você só deve considerar essa abordagem nos cenários extremos listados anteriormente nesta seção.
Não acionem eventos muito rapidamente
Alguns eventos do navegador são disparados com extrema frequência. Por exemplo, onmousemove e onscroll pode disparar dezenas ou centenas de vezes por segundo. Na maioria dos casos, você não precisa executar atualizações de interface do usuário com frequência. Se os eventos forem disparados muito rapidamente, você poderá prejudicar a capacidade de resposta da interface do usuário ou consumir tempo excessivo de CPU.
Em vez de usar eventos nativos que disparam rapidamente, considere o uso da interoperabilidade JS para registrar um retorno de chamada disparado com menos frequência. Por exemplo, o seguinte componente exibe a posição do mouse, mas só é atualizado no máximo uma vez a cada 500 ms:
@implements IDisposable
@inject IJSRuntime JS
<h1>@message</h1>
<div @ref="mouseMoveElement" style="border:1px dashed red;height:200px;">
Move mouse here
</div>
@code {
private ElementReference mouseMoveElement;
private DotNetObjectReference<MyComponent>? selfReference;
private string message = "Move the mouse in the box";
[JSInvokable]
public void HandleMouseMove(int x, int y)
{
message = $"Mouse move at {x}, {y}";
StateHasChanged();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
selfReference = DotNetObjectReference.Create(this);
var minInterval = 500;
await JS.InvokeVoidAsync("onThrottledMouseMove",
mouseMoveElement, selfReference, minInterval);
}
}
public void Dispose() => selfReference?.Dispose();
}
O código JavaScript correspondente registra o ouvinte de eventos do DOM para movimentação do mouse. Neste exemplo, o ouvinte de eventos usa a função de throttle Lodash para limitar a taxa de invocações:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
<script>
function onThrottledMouseMove(elem, component, interval) {
elem.addEventListener('mousemove', _.throttle(e => {
component.invokeMethodAsync('HandleMouseMove', e.offsetX, e.offsetY);
}, interval));
}
</script>
Evite renderizar novamente após a manipulação de eventos sem alterações de estado
Os componentes herdam de ComponentBase, que invoca StateHasChanged automaticamente após os manipuladores de eventos do componente serem acionados. Em alguns casos, pode ser desnecessário ou indesejável disparar uma nova renderização, depois que um manipulador de eventos é invocado. Por exemplo, um manipulador de eventos pode não modificar o estado do componente. Nesses cenários, o aplicativo pode utilizar a interface IHandleEvent para controlar o processamento de eventos de Blazor.
Observação
A abordagem apresentada nesta seção não transmite exceções para os limites de erro. Para obter mais informações e um código de demonstração que utilize limites de erro ao chamar ComponentBase.DispatchExceptionAsync, consulte AsNonRenderingEventHandler + ErrorBoundary = comportamento inesperado (dotnet/aspnetcore #54543).
Para evitar re-renderizações para todos os manipuladores de eventos de um componente, implemente IHandleEvent e forneça uma tarefa IHandleEvent.HandleEventAsync que invoque o manipulador de eventos sem chamar StateHasChanged.
No exemplo a seguir, nenhum manipulador de eventos adicionado ao componente dispara um rerender, portanto HandleSelect , não resulta em um rerender quando invocado.
HandleSelect1.razor:
@page "/handle-select-1"
@using Microsoft.Extensions.Logging
@implements IHandleEvent
@inject ILogger<HandleSelect1> Logger
<p>
Last render DateTime: @dt
</p>
<button @onclick="HandleSelect">
Select me (Avoids Rerender)
</button>
@code {
private DateTime dt = DateTime.Now;
private void HandleSelect()
{
dt = DateTime.Now;
Logger.LogInformation("This event handler doesn't trigger a rerender.");
}
Task IHandleEvent.HandleEventAsync(
EventCallbackWorkItem callback, object? arg) => callback.InvokeAsync(arg);
}
Além de evitar novas renderizações depois que os manipuladores de eventos forem acionados a um componente de forma global, é possível evitar novas renderizações após um único manipulador de eventos, empregando o método de utilitário a seguir.
Adicione a classe a seguir EventUtil a um Blazor aplicativo. As ações e funções estáticas na parte superior da EventUtil classe fornecem manipuladores que abrangem várias combinações de argumentos e tipos de retorno que Blazor usam ao lidar com eventos.
EventUtil.cs:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public static class EventUtil
{
public static Action AsNonRenderingEventHandler(Action callback)
=> new SyncReceiver(callback).Invoke;
public static Action<TValue> AsNonRenderingEventHandler<TValue>(
Action<TValue> callback)
=> new SyncReceiver<TValue>(callback).Invoke;
public static Func<Task> AsNonRenderingEventHandler(Func<Task> callback)
=> new AsyncReceiver(callback).Invoke;
public static Func<TValue, Task> AsNonRenderingEventHandler<TValue>(
Func<TValue, Task> callback)
=> new AsyncReceiver<TValue>(callback).Invoke;
private record SyncReceiver(Action callback)
: ReceiverBase { public void Invoke() => callback(); }
private record SyncReceiver<T>(Action<T> callback)
: ReceiverBase { public void Invoke(T arg) => callback(arg); }
private record AsyncReceiver(Func<Task> callback)
: ReceiverBase { public Task Invoke() => callback(); }
private record AsyncReceiver<T>(Func<T, Task> callback)
: ReceiverBase { public Task Invoke(T arg) => callback(arg); }
private record ReceiverBase : IHandleEvent
{
public Task HandleEventAsync(EventCallbackWorkItem item, object arg) =>
item.InvokeAsync(arg);
}
}
Chame EventUtil.AsNonRenderingEventHandler para chamar um manipulador de eventos que não dispara uma renderização quando invocado.
No exemplo a seguir:
- Selecionar o primeiro botão, que chama
HandleClick1, dispara uma nova renderização. - Selecionar o segundo botão, que chama
HandleClick2, não dispara uma nova renderização. - Selecionar o terceiro botão, que chama
HandleClick3, não dispara uma nova renderização e usa argumentos de evento (MouseEventArgs).
HandleSelect2.razor:
@page "/handle-select-2"
@using Microsoft.Extensions.Logging
@inject ILogger<HandleSelect2> Logger
<p>
Last render DateTime: @dt
</p>
<button @onclick="HandleClick1">
Select me (Rerenders)
</button>
<button @onclick="EventUtil.AsNonRenderingEventHandler(HandleClick2)">
Select me (Avoids Rerender)
</button>
<button @onclick="EventUtil.AsNonRenderingEventHandler<MouseEventArgs>(HandleClick3)">
Select me (Avoids Rerender and uses <code>MouseEventArgs</code>)
</button>
@code {
private DateTime dt = DateTime.Now;
private void HandleClick1()
{
dt = DateTime.Now;
Logger.LogInformation("This event handler triggers a rerender.");
}
private void HandleClick2()
{
dt = DateTime.Now;
Logger.LogInformation("This event handler doesn't trigger a rerender.");
}
private void HandleClick3(MouseEventArgs args)
{
dt = DateTime.Now;
Logger.LogInformation(
"This event handler doesn't trigger a rerender. " +
"Mouse coordinates: {ScreenX}:{ScreenY}",
args.ScreenX, args.ScreenY);
}
}
Além de implementar a IHandleEvent interface, aproveitar as outras práticas recomendadas descritas neste artigo também pode ajudar a reduzir renderizações indesejadas depois que os eventos são tratados. Por exemplo, a substituição de ShouldRender nos componentes filho do componente de destino pode ser usada para controlar a nova renderização.
Evite recriar delegados para muitos elementos ou componentes repetidos
BlazorA recriação de delegados de expressões lambda para elementos ou componentes em um loop pode resultar em baixo desempenho.
O componente a seguir mostrado no artigo de manipulação de eventos renderiza um conjunto de botões. Cada botão atribui um delegado ao evento @onclick , o que é bom se não houver muitos botões a serem renderizados.
EventHandlerExample5.razor:
@page "/event-handler-example-5"
<h1>@heading</h1>
@for (var i = 1; i < 4; i++)
{
var buttonNumber = i;
<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
private void UpdateHeading(MouseEventArgs e, int buttonNumber)
{
heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}
}
@page "/event-handler-example-5"
<h1>@heading</h1>
@for (var i = 1; i < 4; i++)
{
var buttonNumber = i;
<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
private void UpdateHeading(MouseEventArgs e, int buttonNumber)
{
heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}
}
Se um grande número de botões for renderizado usando a abordagem anterior, a velocidade de renderização será afetada negativamente, levando a uma experiência ruim do usuário. Para renderizar um grande número de botões com um retorno de chamada para eventos de clique, o exemplo a seguir usa uma coleção de objetos de botão que atribuem o delegado @onclick de cada botão a um Action. A abordagem a seguir não requer Blazor para recriar todos os delegados de botão sempre que os botões são renderizados:
LambdaEventPerformance.razor:
@page "/lambda-event-performance"
<h1>@heading</h1>
@foreach (var button in Buttons)
{
<p>
<button @key="button.Id" @onclick="button.Action">
Button #@button.Id
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
private List<Button> Buttons { get; set; } = new();
protected override void OnInitialized()
{
for (var i = 0; i < 100; i++)
{
var button = new Button();
button.Id = Guid.NewGuid().ToString();
button.Action = (e) =>
{
UpdateHeading(button, e);
};
Buttons.Add(button);
}
}
private void UpdateHeading(Button button, MouseEventArgs e)
{
heading = $"Selected #{button.Id} at {e.ClientX}:{e.ClientY}";
}
private class Button
{
public string? Id { get; set; }
public Action<MouseEventArgs> Action { get; set; } = e => { };
}
}