Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. 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 .NET 10 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 do .NET Core. Para a versão atual, consulte a versão .NET 9 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 gerar uma melhoria de dez vezes ou mais na velocidade de renderização da interface do usuário.
Evite a renderização desnecessária de subárvores de componentes
Talvez seja possível remover a maior parte do custo de renderização de um componente pai ignorando a rerenderização das subárvores dos componentes filhos quando ocorre um evento. Você só deve se preocupar em ignorar a rerenderização de subárvores que são particularmente caras de renderizar e estão a causar lentidão na interface do utilizador.
No tempo de execução, os componentes existem em uma hierarquia. Um componente raiz (o primeiro componente carregado) tem componentes filhos. Por sua vez, os filhos da raiz têm os seus próprios subcomponentes, e assim por diante. Quando ocorre um evento, como um usuário selecionando um botão, o processo a seguir determina quais componentes devem ser renderizados novamente:
- O evento é enviado para o componente que processou o manipulador do evento. Depois de executar o manipulador de eventos, o componente é rerenderizado.
- Quando um componente é renderizado novamente, fornece uma nova cópia dos valores de parâmetros para cada um de seus componentes filhos.
- Depois que um novo conjunto de valores de parâmetro é recebido, Blazor decide se deseja renderizar novamente o componente. Os componentes são rerenderizados se
ShouldRenderretornaremtrue, que é o comportamento padrão, a menos que sejam substituídos, e os valores dos parâmetros podem ter sido alterados, por exemplo, se forem objetos mutáveis.
As duas últimas etapas da sequência anterior continuam recursivamente para baixo na hierarquia de componentes. Em muitos casos, toda a subárvore é rerenderizada. Eventos direcionados a componentes de alto nível podem causar rerenderização dispendiosa porque 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:
- Certifique-se de que os parâmetros do componente filho sejam de tipos imutáveis específicos†, como
string,int,booleDateTime. A lógica interna para detetar alterações ignora automaticamente a rerenderização se os valores de parâmetros imutáveis não forem alterados. Se renderizar um componente filho com<Customer CustomerId="item.CustomerId" />, ondeCustomerIdé um tipoint, então o componenteCustomernão será reprocessado, a menos queitem.CustomerIdseja modificado. - Substituir
ShouldRender, retornandofalse:- Quando os parâmetros são tipos não primitivos ou tipos imutáveis não suportados†, como tipos de modelo personalizados complexos ou valores RenderFragment, e os valores dos parâmetros não foram alterados,
- Se a criação de um componente somente da interface do usuário não for 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 deteção de alterações na fonte de referência do Blazor (ChangeDetection.cs).
Observação
Os links de documentação para a 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 tag para uma versão específica, use a lista suspensa Alternar entre ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte ASP.NET Core (dotnet/AspNetCore.Docs #26205).
O exemplo de ferramenta de pesquisa de voos de companhias aéreas a seguir usa campos privados para rastrear as informações necessárias para detetar alterações. O identificador de voo de entrada anterior (prevInboundFlightId) e o identificador de voo de saída anterior (prevOutboundFlightId) rastreiam informações para a próxima atualização de componente potencial. Se qualquer um dos identificadores de voo mudar quando os parâmetros do componente forem definidos em OnParametersSet, o componente será reprocessado porque shouldRender está definido como true. Se shouldRender for avaliado como false após a verificação dos identificadores de voo, evita-se um novo processamento dispendioso.
@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 rerenderização no nível de manipuladores de eventos individuais geralmente não é necessário.
Para obter mais informações, consulte os seguintes recursos:
- ASP.NET Core Razor ciclo de vida do componente
- ShouldRender
- Renderização de componentes do ASP.NET Core Razor
Virtualização
Ao renderizar grandes quantidades de interface do usuário dentro de 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 rolagem, 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 arbitrariamente grande enquanto renderiza apenas os itens de lista que estão dentro do viewport de rolagem 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, consulte ASP.NET Core Razor component virtualization.
Crie componentes leves e otimizados
A maioria dos componentes Razor não requer esforços de otimização agressivos porque a maioria dos componentes não se repete na interface de utilizador e não é redesenhada com alta frequência. Por exemplo, componentes roteáveis com uma diretiva @page e componentes usados para renderizar elementos de nível superior da interface do utilizador, como caixas de diálogo ou formulários, provavelmente aparecem apenas um de cada vez e só são renderizados novamente em resposta a uma ação do utilizador. Esses componentes geralmente não criam alta carga de trabalho de renderização, 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 baixo desempenho da interface do usuário:
- Grandes formulários aninhados com centenas de elementos individuais, como entradas ou rótulos.
- Grades com centenas de linhas ou milhares de células.
- Gráficos de dispersão com milhões de pontos de dados.
Se modelar cada elemento, célula ou ponto de dados como uma instância de componente separada, geralmente há tantos deles que seu 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.
Evite milhares de instâncias de componentes
Cada componente é uma ilha separada que pode render independentemente de seus pais e filhos. Ao escolher como dividir a interface do usuário em uma hierarquia de componentes, você assume o controle sobre a granularidade da renderização da interface do usuário. Isso pode resultar em bom ou mau desempenho.
Ao dividir a interface do utilizador em componentes separados, é possível que partes menores da interface do usuário sejam atualizadas quando ocorrerem eventos. Numa tabela com muitas linhas, onde há um botão em cada linha, pode conseguir renderizar novamente apenas a linha específica utilizando 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 ASP.NET Core, uma sobrecarga de renderização de cerca de 0,06 ms por instância de componente foi observada em um aplicativo Blazor WebAssembly. O aplicativo de teste renderizou um componente simples que aceita três parâmetros. Internamente, a sobrecarga é em grande parte devida à recuperação do estado de cada componente a partir de dicionários e à passagem e receção de parâmetros. Por multiplicação, você pode ver que adicionar 2.000 instâncias de componentes extras adicionaria 0,12 segundos ao tempo de renderização e a interface do usuário começaria a parecer 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 é muitas vezes evitar ter tantos componentes para renderizar. As seções a seguir descrevem duas abordagens que você pode adotar.
Para obter mais informações sobre gerenciamento de memória, consulte Gerenciar memória em aplicativos implantados do lado Blazor do servidor ASP.NET Core.
Componentes filho dentro dos seus pais: Considere a seguinte parte de um componente pai que renderiza componentes filho num 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 funciona bem se milhares de mensagens não forem mostradas de uma só vez. Para mostrar milhares de mensagens de uma só vez, considere não extrair o componente ChatMessageDisplay separado. Em vez disso, incorpore o componente filho no pai. **
A abordagem a seguir evita a sobrecarga por componente de renderizar tantos componentes filhos, à custa de se perder a capacidade de rerenderizar o markup 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>
Definir reutilizável RenderFragments no código: pode estar a separar componentes filhos puramente como uma forma de reutilizar a lógica de renderização. Se esse for o caso, você pode criar lógica de renderização reutilizável sem implementar componentes adicionais. No bloco @code de qualquer componente, defina um RenderFragment. Renderize o fragmento de qualquer local quantas vezes forem necessárias:
@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 código reutilizável em múltiplos componentes, declare o RenderFragmentpublic e static:
public static RenderFragment SayHello = @<h1>Hello!</h1>;
SayHello no exemplo anterior pode ser invocado a partir de um componente não relacionado. Essa técnica é útil para criar bibliotecas de trechos de marcação reutilizáveis que são renderizados sem sobrecarga por componente.
RenderFragment delegados podem aceitar parâmetros. O componente a seguir 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 utilizador de forma independente, nem tem a capacidade de ignorar a renderização da subárvore da interface do utilizador quando o seu elemento pai é renderizado, porque não existe uma fronteira de componente. A atribuição a um delegado RenderFragment apenas é suportada em ficheiros de componentes 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 o RenderFragment:
protected RenderFragment DisplayTitle =>
@<div>
@TitleTemplate
</div>;
Não receba muitos parâmetros
Se um componente se repete com extrema frequência, por exemplo, centenas ou milhares de vezes, a sobrecarga de passar e receber cada parâmetro se acumula.
É raro que muitos parâmetros restrinjam severamente o desempenho, mas isso pode ser um fator. Para um componente TableCell que renderiza 4.000 vezes dentro de 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 de parâmetros, 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, também afeta como a deteção de alterações e a renderização se comportam. Passar parâmetros não primitivos sempre dispara uma rerenderização, porque Blazor não pode saber se objetos arbitrários têm estado internamente mutável, enquanto passar parâmetros primitivos só dispara uma rerenderização se seus valores tiverem sido realmente alterados.
Além disso, considere que pode ser uma melhoria não ter um componente de célula de tabela, como 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, a avaliação comparativa das abordagens é geralmente necessária para determinar qual abordagem produz os melhores resultados.
Para obter mais informações sobre parâmetros de tipo genéricos (@typeparam), consulte os seguintes recursos:
- Razor referência de sintaxe para ASP.NET Core
- ASP.NET componentes principais Razor
- ASP.NET Core Blazor componentes modelados
Garantir que os parâmetros em cascata sejam corrigidos
O componente CascadingValue tem um parâmetro IsFixed opcional:
- Se
IsFixedforfalse(padrão), cada destinatário do valor em cascata configura uma subscrição para receber notificações de alteração. Cada[CascadingParameter]é substancialmente mais caro do que um[Parameter]normal devido ao rastreamento da assinatura. - Se
IsFixedfortrue(por exemplo,<CascadingValue Value="someValue" IsFixed="true">), os destinatários receberão o valor inicial, mas não configurarão uma assinatura para receber atualizações. Cada[CascadingParameter]é leve e não é mais caro do que um[Parameter]normal.
Definir IsFixed como 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 para true em valores em cascata. Você pode definir IsFixed como true quando o valor fornecido não muda 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 muda durante o ciclo de vida do componente:
<CascadingValue Value="this" IsFixed="true">
<SomeOtherComponents>
</CascadingValue>
Para obter mais informações, consulte ASP.NET Core Blazor valores e parâmetros em cascata.
Evite a dispersão de atributos com CaptureUnmatchedValues
Os componentes podem optar por receber valores de parâmetros "incomparáveis" usando o sinalizador CaptureUnmatchedValues:
<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:
- Corresponder todos os parâmetros fornecidos com o conjunto de parâmetros conhecidos para criar um dicionário.
- Acompanhe como várias cópias do mesmo atributo se substituem.
Use CaptureUnmatchedValues em que 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 numa lista longa ou nas células de uma grelha, tente evitar o splatting de atributos.
Para obter mais informações, consulte ASP.NET Core Blazor atributo splatting e parâmetros arbitrários.
Implementar SetParametersAsync manualmente
Uma fonte significativa de sobrecarga de renderização por componente é gravar valores de parâmetros de entrada em propriedades [Parameter]. O renderizador utiliza a reflexão para escrever os valores dos parâmetros, o que pode levar a um desempenho ruim em grande escala.
Em alguns casos extremos, você pode querer evitar a reflexão e implementar sua própria lógica de configuração de parâmetros manualmente. Isto pode ser aplicável quando:
- Um componente é renderizado com extrema 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 dos parâmetros de recebimento tem um impacto observável na capacidade de resposta da interface do usuário.
Em casos extremos, pode-se substituir o método virtual SetParametersAsync do componente e implementar a 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 base SetParametersAsync 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, por isso geralmente não recomendamos a adoção dessa abordagem. Em casos extremos, ele 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 acione eventos muito rapidamente
Alguns eventos do navegador são acionados com extrema frequência. Por exemplo, onmousemove e onscroll podem disparar dezenas ou centenas de vezes por segundo. Na maioria dos casos, você não precisa executar atualizações da interface do usuário com frequência. Se os eventos forem acionados muito rapidamente, você poderá prejudicar a capacidade de resposta da interface do usuário ou consumir tempo excessivo da CPU.
Em vez de usar eventos nativos que disparam rapidamente, considere utilizar a interoperabilidade do JS para registar uma função de retorno que é acionada com menos frequência. Por exemplo, o componente a seguir exibe a posição do mouse, mas só atualiza 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 DOM para o movimento do mouse. Neste exemplo, o ouvinte de eventos usa a função do
<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 rerenderização ao manipular eventos sem alterações de estado
Os componentes herdam do ComponentBase, que invoca automaticamente StateHasChanged depois que os manipuladores de eventos do componente são invocados. Em alguns casos, pode ser desnecessário ou indesejável provocar uma re-renderização quando um manipulador de eventos é invocado. Por exemplo, um manipulador de eventos pode não modificar o estado do componente. Nesses cenários, a aplicação pode utilizar a interface IHandleEvent para controlar o comportamento do tratamento de eventos do Blazor.
Observação
A abordagem nesta seção não encaminha exceções para os limites de erro . Para obter mais informações e código de demonstração que ofereça suporte a limites de erro chamando ComponentBase.DispatchExceptionAsync, consulte AsNonRenderingEventHandler + ErrorBoundary = comportamento inesperado (dotnet/aspnetcore #54543).
Para evitar rerenderizaçõ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 uma rerenderização, portanto, HandleSelect não resulta em uma rerenderização 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 rerenderizações depois que manipuladores de eventos são acionados em um componente de forma global, é possível evitar rerenderizações após um único manipulador de eventos empregando o seguinte método utilitário.
Adicione a seguinte classe EventUtil a um aplicativo Blazor. As ações e funções estáticas na parte superior da classe EventUtil fornecem manipuladores que abrangem várias combinações de argumentos e tipos de retorno que Blazor usa ao manipular 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 acione uma renderização quando invocado.
No exemplo a seguir:
- Selecionar o primeiro botão, que chama
HandleClick1, dispara uma re-renderização. - Selecionar o segundo botão, que chama
HandleClick2, não provoca uma nova renderização. - Selecionar o terceiro botão, que chama
HandleClick3, não aciona uma re-renderização e utiliza os 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 interface IHandleEvent, aproveitar as outras práticas recomendadas descritas neste artigo também pode ajudar a reduzir renderizações indesejadas depois que os eventos são manipulados. Por exemplo, a substituição de ShouldRender nos componentes filho do componente de destino pode ser utilizada para controlar a re-renderização.
Evite recriar delegados para elementos ou componentes usados repetidamente
Blazorrecriação de expressão lambda delega para elementos ou componentes em um loop pode levar a um desempenho ruim.
O componente a seguir mostrado no artigo de manipulação de eventos renderiza um conjunto de botões. Cada botão aloca um delegado ao seu evento @onclick, o que não é problemático se não houver muitos botões para renderizar.
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 de usuário ruim. 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ões que atribuem o delegado @onclick de cada botão a um Action. A abordagem a seguir não exige que Blazor reconstrua todos os delegados de botão cada vez 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 => { };
}
}