Delen via


Best practices voor ASP.NET Core Blazor rendering

Opmerking

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 10-versie van dit artikel voor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie het .NET- en .NET Core-ondersteuningsbeleid voor meer informatie. Zie de .NET 9-versie van dit artikel voor de huidige release.

Optimaliseer de renderingsnelheid om de renderingworkload te minimaliseren en de reactiesnelheid van de gebruikersinterface te verbeteren. Dit kan leiden tot een tienvoudige of hogere verbetering in de weergavesnelheid van de gebruikersinterface.

Vermijd onnodige weergave van onderdeelsubstructuren

Mogelijk kunt u het merendeel van de weergavekosten van een ouderonderdeel verminderen door de herweergave van onderdelen van kindcomponenten over te slaan bij een gebeurtenis. Je hoeft je alleen zorgen te maken over het overslaan van de hertekeningsafdelingen die bijzonder veel rekenkracht vereisen en die UI-vertraging veroorzaken.

Tijdens runtime bestaan onderdelen in een hiërarchie. Een hoofdonderdeel (het eerste geladen onderdeel) bevat onderliggende onderdelen. De kinderen van de wortel hebben hun eigen kindcomponenten, en zo verder. Wanneer een gebeurtenis plaatsvindt, zoals een gebruiker die een knop selecteert, bepaalt het volgende proces welke onderdelen opnieuw moeten worden gebruikt:

  1. De gebeurtenis wordt gestuurd naar de component die de gebeurtenishandler heeft gerenderd. Nadat de evenementhandler is uitgevoerd, wordt het onderdeel hergerenderd.
  2. Wanneer een onderdeel opnieuw wordt gebruikt, wordt er een nieuwe kopie van parameterwaarden aan elk van de onderliggende onderdelen geleverd.
  3. Nadat een nieuwe set parameterwaarden is ontvangen, Blazor bepaalt u of het onderdeel opnieuw moet worden uitgevoerd. Onderdelen worden opnieuw gerenderd als ShouldRendertrue retourneert, wat het standaardgedrag is, tenzij het wordt overschreven, en de parameterwaarden kunnen zijn gewijzigd, bijvoorbeeld als het veranderlijke objecten zijn.

De laatste twee stappen van de voorgaande reeks worden recursief voortgezet binnen de componenthiërarchie. In veel gevallen wordt de hele subboom hergerenderd. Gebeurtenissen die gericht zijn op onderdelen op hoog niveau kunnen dure rerendering veroorzaken, omdat elk onderdeel onder het onderdeel op hoog niveau opnieuw moet worden uitgevoerd.

Gebruik een van de volgende benaderingen om recursie in een bepaalde substructuur te voorkomen:

  • Zorg ervoor dat de parameters van onderliggende onderdelen van specifieke onveranderbare typen zijn†, zoals string, inten boolDateTime. De ingebouwde logica voor het detecteren van wijzigingen slaat automatisch rerendering over als de onveranderbare parameterwaarden niet zijn gewijzigd. Als u een onderliggend onderdeel met <Customer CustomerId="item.CustomerId" />weergeeft, waarbij CustomerId een int type is, wordt het Customer onderdeel niet gewijzigd, tenzij item.CustomerId wijzigingen aanbrengt.
  • Overschrijving ShouldRender, retourneert false:
    • Wanneer parameters niet-primieve typen of niet-ondersteunde onveranderbare typen zijn†, zoals complexe aangepaste modeltypen of RenderFragment -waarden, en de parameterwaarden niet zijn gewijzigd,
    • Als u een UI-alleen onderdeel maakt dat niet verandert na de eerste weergave, ongeacht of de parameterwaarde verandert.

† Zie de logica voor wijzigingsdetectie in Blazorde referentiebron (ChangeDetection.cs)) voor meer informatie.

Opmerking

Documentatiekoppelingen naar .NET-referentiebron laden meestal de standaardbranch van de opslagplaats, die de huidige ontwikkeling vertegenwoordigt voor de volgende release van .NET. Als u een tag voor een specifieke release wilt selecteren, gebruikt u de Switch branches of tags vervolgkeuzelijst. Zie Een versietag selecteren van ASP.NET Core-broncode (dotnet/AspNetCore.Docs #26205)voor meer informatie.

In het volgende voorbeeld van een vluchtzoekprogramma voor luchtvaartmaatschappijen worden privévelden gebruikt om de benodigde informatie bij te houden om wijzigingen te detecteren. De vorige binnenkomende vlucht-id (prevInboundFlightId) en de vorige uitgaande vlucht-id (prevOutboundFlightId) houden informatie bij voor de volgende mogelijke componentupdate. Als een van de flight-id's verandert wanneer de parameters van het onderdeel zijn ingesteld in OnParametersSet, wordt het onderdeel opnieuw gebruikt omdat shouldRender is ingesteld op true. Als shouldRender na de controle van de vlucht-id's overeenkomt met false, wordt een dure rerender vermeden.

@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;
}

Een gebeurtenis-handler kan ook shouldRender instellen op true. Voor de meeste onderdelen is het bepalen van rerendering op het niveau van afzonderlijke gebeurtenis-handlers meestal niet nodig.

Zie de volgende bronnen voor meer informatie:

Virtualisatie

Bij het weergeven van grote hoeveelheden gebruikersinterface binnen een lus, bijvoorbeeld een lijst of raster met duizenden vermeldingen, kan het grote aantal renderingbewerkingen leiden tot vertraging in de rendering van de gebruikersinterface. Aangezien de gebruiker slechts een klein aantal elementen tegelijk kan zien zonder te schuiven, is het vaak verspilling om tijd te besteden aan het weergeven van elementen die momenteel niet zichtbaar zijn.

Blazor biedt het Virtualize<TItem> onderdeel om het uiterlijk en schuifgedrag van een willekeurig grote lijst te maken, terwijl alleen de lijstitems worden weergegeven die zich in de huidige schuifweergavepoort bevinden. Een onderdeel kan bijvoorbeeld een lijst met 100.000 vermeldingen weergeven, maar alleen de renderingkosten betalen van 20 items die zichtbaar zijn.

Zie ASP.NET Core Razor componentvirtualisatievoor meer informatie.

Lichtgewicht, geoptimaliseerde onderdelen maken

De meeste Razor onderdelen vereisen geen agressieve optimalisatie-inspanningen omdat de meeste onderdelen niet worden herhaald in de gebruikersinterface en niet met hoge frequentie opnieuw worden uitgevoerd. Routeerbare onderdelen met een @page-instructie en onderdelen die worden gebruikt om onderdelen van de gebruikersinterface op hoog niveau weer te geven, zoals dialoogvensters of formulieren, worden waarschijnlijk slechts één voor één weergegeven en worden alleen opnieuw uitgevoerd als reactie op een gebruikersbeweging. Deze onderdelen maken meestal geen hoge renderingworkload, dus u kunt vrijelijk elke combinatie van frameworkfuncties gebruiken zonder dat u veel zorgen hoeft te maken over renderingprestaties.

Er zijn echter veelvoorkomende scenario's waarbij onderdelen op schaal worden herhaald en vaak leiden tot slechte ui-prestaties:

  • Grote geneste formulieren met honderden afzonderlijke elementen, zoals invoervelden of labelteksten.
  • Rasters met honderden rijen of duizenden cellen.
  • Spreidingsdiagrammen met miljoenen gegevenspunten.

Als u elk element, elke cel of elk gegevenspunt modelleert als een afzonderlijk onderdeelexemplaar, zijn er vaak zoveel dat de renderingprestaties cruciaal zijn. In deze sectie vindt u advies over het lichtgewicht maken van dergelijke onderdelen, zodat de gebruikersinterface snel en responsief blijft.

Vermijd duizenden instantiaties van onderdelen

Elk onderdeel is een afzonderlijk eiland dat onafhankelijk van zijn ouders en kinderen kan worden weergegeven. Door te kiezen hoe u de gebruikersinterface splitst in een hiërarchie van onderdelen, neemt u de controle over de granulariteit van ui-rendering. Dit kan leiden tot goede of slechte prestaties.

Door de gebruikersinterface te splitsen in afzonderlijke onderdelen, kunt u kleinere delen van de gebruikersinterface opnieuw genereren wanneer er gebeurtenissen plaatsvinden. In een tabel met veel rijen met een knop in elke rij kunt u mogelijk slechts één rij opnieuw maken met behulp van een onderliggend onderdeel in plaats van de hele pagina of tabel. Elk onderdeel vereist echter extra geheugen- en CPU-overhead om te kunnen omgaan met de onafhankelijke status en renderinglevenscyclus.

In een test die is uitgevoerd door de technici van de ASP.NET Core-producteenheid, werd een rendering-overhead van ongeveer 0,06 ms per componentinstantie waargenomen bij een Blazor WebAssembly-app. De test-app heeft een eenvoudig onderdeel weergegeven dat drie parameters accepteert. Intern is de overhead grotendeels te wijten aan het ophalen van de status per onderdeel uit woordenlijsten en het doorgeven en ontvangen van parameters. Door te vermenigvuldigen kunt u zien dat het toevoegen van 2000 extra onderdeelexemplaren 0,12 seconden zou toevoegen aan de renderingtijd en dat de gebruikersinterface langzaam zou beginnen te voelen voor gebruikers.

Het is mogelijk om onderdelen lichter te maken, zodat u er meer van kunt hebben. Een krachtigere techniek is echter vaak om te voorkomen dat zoveel onderdelen worden weergegeven. In de volgende secties worden twee benaderingen beschreven die u kunt gebruiken.

Zie Geheugen beheren in geïmplementeerde ASP.NET Core-apps aan de serverzijde Blazorvoor meer informatie over geheugenbeheer.

Voeg kindcomponenten inline in hun ouders in: Houd rekening met het volgende gedeelte van een oudercomponent dat kindcomponenten in een loop weergeeft:

<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; }
}

Het voorgaande voorbeeld presteert goed als duizenden berichten niet tegelijk worden weergegeven. Als u duizenden berichten tegelijk wilt weergeven, kunt u overwegen niet het afzonderlijke ChatMessageDisplay onderdeel uit te factoren. In plaats daarvan, plaats het kindonderdeel inline in de bovenliggende component. De volgende aanpak voorkomt de overhead per component van het weergeven van zoveel subcomponenten, ten koste van de mogelijkheid om de markering van elk subcomponent onafhankelijk opnieuw te renderen.

<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>

Herbruikbaar RenderFragments definiëren in code: U kunt subcomponenten misschien alleen factoreren om de renderinglogica herbruikbaar te maken. Als dat het geval is, kunt u herbruikbare renderinglogica maken zonder extra onderdelen te implementeren. Definieer een @codein het RenderFragment blok van een onderdeel. Render het fragment vanuit elke locatie zo vaak als nodig is.

@RenderWelcomeInfo

<p>Render the welcome content a second time:</p>

@RenderWelcomeInfo

@code {
    private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!</p>;
}

Als u RenderTreeBuilder code herbruikbaar wilt maken voor meerdere onderdelen, declareert u de RenderFragmentpublic en static:

public static RenderFragment SayHello = @<h1>Hello!</h1>;

SayHello in het voorgaande voorbeeld kan worden aangeroepen vanuit een niet-gerelateerd onderdeel. Deze techniek is handig voor het bouwen van bibliotheken met herbruikbare markeringsfragmenten die worden weergegeven zonder overhead per onderdeel.

RenderFragment delegeren kunnen parameters accepteren. Het volgende onderdeel geeft het bericht (message) door aan de RenderFragment gedelegeerde:

<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>;
}

De voorgaande benadering hergebruikt renderinglogica zonder overhead per onderdeel. De methode staat echter niet toe dat de substructuur van de gebruikersinterface onafhankelijk wordt vernieuwd, noch heeft deze de mogelijkheid om de substructuur van de gebruikersinterface over te slaan wanneer het bovenliggende element wordt weergegeven omdat er geen onderdeelgrens is. Toewijzing aan een RenderFragment gedelegeerde wordt alleen ondersteund in Razor onderdeelbestanden (.razor).

Gebruik voor een niet-statisch veld, een methode of een eigenschap waarnaar niet kan worden verwezen door een initialisatiefunctie voor velden, zoals TitleTemplate in het volgende voorbeeld, een eigenschap in plaats van een veld voor de RenderFragment:

protected RenderFragment DisplayTitle =>
    @<div>
        @TitleTemplate
    </div>;

Niet te veel parameters ontvangen

Als een onderdeel extreem vaak wordt herhaald, bijvoorbeeld honderden of duizenden keren, wordt de overhead van het doorgeven en ontvangen van elke parameter opgebouwd.

Het is zeldzaam dat te veel parameters de prestaties ernstig beperken, maar dit kan een factor zijn. Voor een TableCell-onderdeel dat 4000 keer binnen een raster weergeeft, voegt elke parameter die aan het onderdeel wordt doorgegeven ongeveer 15 ms toe aan de totale renderingkosten. Het doorgeven van tien parameters vereist ongeveer 150 ms en veroorzaakt een vertraging in de weergave van de gebruikersinterface.

Als u de belasting van de parameter wilt verminderen, bundelt u meerdere parameters in een aangepaste klasse. Een tabelcelonderdeel kan bijvoorbeeld een gemeenschappelijk object accepteren. In het volgende voorbeeld is Data voor elke cel anders, maar Options is gebruikelijk in alle celexemplaren:

@typeparam TItem

...

@code {
    [Parameter]
    public TItem? Data { get; set; }
    
    [Parameter]
    public GridOptions? Options { get; set; }
}

Houd er echter rekening mee dat het bundelen van primitieve parameters in een klasse niet altijd een voordeel is. Hoewel het aantal parameters kan verminderen, heeft dit ook invloed op de werking van wijzigingsdetectie en -rendering. Het doorgeven van niet-primitieve parameters activeert altijd een herweergave, omdat Blazor niet kan weten of willekeurige objecten intern veranderlijk zijn, terwijl het doorgeven van primitieve parameters alleen een herweergave activeert als de waarden daadwerkelijk zijn gewijzigd.

Houd er ook rekening mee dat het mogelijk een verbetering is om geen tabelcelonderdeel te hebben, zoals wordt weergegeven in het vorige voorbeeld, en in plaats daarvan inline de logica ervan in het bovenliggende onderdeel.

Opmerking

Wanneer er meerdere benaderingen beschikbaar zijn voor het verbeteren van de prestaties, is benchmarking van de benaderingen meestal vereist om te bepalen welke benadering de beste resultaten oplevert.

Zie de volgende bronnen voor meer informatie over algemene typeparameters (@typeparam):

Zorg ervoor dat trapsgewijze parameters zijn vastgezet

Het CascadingValue-onderdeel heeft een optionele IsFixed parameter:

  • Als IsFixed is false (de standaardinstelling), stelt elke ontvanger van de trapsgewijze waarde een abonnement in om wijzigingsmeldingen te ontvangen. Elke [CascadingParameter] is veel duurder dan een gewone [Parameter] vanwege het abonnementsbeheer.
  • Als IsFixed is true (bijvoorbeeld <CascadingValue Value="someValue" IsFixed="true">), ontvangen ontvangers de oorspronkelijke waarde, maar stellen ze geen abonnement in om updates te ontvangen. Elke [CascadingParameter] is lichtgewicht en is niet duurder dan een gewone [Parameter].

Als u IsFixed instelt op true verbetert u de prestaties als er een groot aantal andere onderdelen is die de trapsgewijze waarde ontvangen. Stel IsFixed waar mogelijk in op true voor cascadewaarden. U kunt IsFixed instellen op true wanneer de opgegeven waarde na verloop van tijd niet verandert.

Wanneer een onderdeel this als trapsgewijze waarde doorgeeft, kan IsFixed ook worden ingesteld op true, omdat this nooit verandert tijdens de levenscyclus van het onderdeel:

<CascadingValue Value="this" IsFixed="true">
    <SomeOtherComponents>
</CascadingValue>

Zie ASP.NET Core Blazor trapsgewijze waarden en parametersvoor meer informatie.

Vermijd het verspreiden van attributen met CaptureUnmatchedValues

Onderdelen kunnen ervoor kiezen om niet-overeenkomende parameterwaarden te ontvangen met behulp van de vlag CaptureUnmatchedValues:

<div @attributes="OtherAttributes">...</div>

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public IDictionary<string, object>? OtherAttributes { get; set; }
}

Met deze benadering kunnen willekeurige extra kenmerken aan het element worden doorgegeven. Deze benadering is echter duur omdat de renderer het volgende moet doen:

  • Koppel alle opgegeven parameters aan de set bekende parameters om een woordenlijst te maken.
  • Houd bij hoe meerdere exemplaren van hetzelfde kenmerk elkaar overschrijven.

Gebruik CaptureUnmatchedValues waarbij de prestaties van onderdeelweergave niet kritiek zijn, zoals onderdelen die niet regelmatig worden herhaald. Voor onderdelen die op schaal worden weergegeven, zoals elk item in een grote lijst of in de cellen van een raster, probeert u kenmerksplatting te voorkomen.

Voor meer informatie, zie ASP.NET Core Blazor attribute splatting en willekeurige parameters.

Handmatig SetParametersAsync implementeren

Een belangrijke bron van overhead per onderdeel is het schrijven van binnenkomende parameterwaarden in [Parameter]-eigenschappen. De renderer gebruikt reflectie om de parameterwaarden te schrijven, wat kan leiden tot slechte prestaties bij grootschalig gebruik.

In sommige extreme gevallen wilt u mogelijk de weerspiegeling vermijden en uw eigen logica voor parameters handmatig implementeren. Dit kan van toepassing zijn wanneer:

  • Een onderdeel wordt extreem vaak weergegeven, bijvoorbeeld wanneer er honderden of duizenden kopieën van het onderdeel in de gebruikersinterface zijn.
  • Een onderdeel accepteert veel parameters.
  • U vindt dat de overhead van ontvangende parameters een waarneembare invloed heeft op de reactiesnelheid van de gebruikersinterface.

In extreme gevallen kunt u de methode voor virtuele SetParametersAsync van het onderdeel overschrijven en uw eigen componentspecifieke logica implementeren. In het volgende voorbeeld worden woordenlijstzoekacties opzettelijk vermeden:

@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);
    }
}

In de voorgaande code wordt de basisklasse SetParametersAsync geretourneerd, waardoor de normale levenscyclusmethode wordt uitgevoerd zonder opnieuw parameters toe te wijzen.

Zoals u in de voorgaande code kunt zien, is het overschrijven van SetParametersAsync en het opgeven van aangepaste logica ingewikkeld en arbeidsintensief, dus we raden u over het algemeen niet aan deze benadering te gebruiken. In extreme gevallen kunt u de renderingprestaties verbeteren met 20-25%, maar u moet deze benadering alleen overwegen in de extreme scenario's die eerder in deze sectie zijn vermeld.

Niet te snel gebeurtenissen activeren

Sommige browser-gebeurtenissen worden extreem vaak geactiveerd. onmousemove en onscroll kunnen bijvoorbeeld tientallen of honderden keren per seconde worden geactiveerd. In de meeste gevallen hoeft u de gebruikersinterface niet zo vaak bij te werken. Als gebeurtenissen te snel worden geactiveerd, kan dit de reactietijd van de gebruikersinterface schaden of overmatige CPU-tijd verbruiken.

In plaats van systeemeigen gebeurtenissen te gebruiken die snel worden geactiveerd, kunt u het gebruik van JS interop overwegen om een callback te registreren die minder vaak wordt geactiveerd. In het volgende onderdeel wordt bijvoorbeeld de positie van de muis weergegeven, maar wordt slechts één keer per 500 ms bijgewerkt:

@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();
}

De bijbehorende JavaScript-code registreert de DOM-gebeurtenislistener voor muisbewegingen. In dit voorbeeld gebruikt de gebeurtenislistener de throttle-functie van Lodash om de frequentie van aanroepen te beperken:

<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>

Voorkom rerendering na het afhandelen van gebeurtenissen zonder statuswijzigingen

Onderdelen nemen over van ComponentBase, die automatisch StateHasChanged aanroept nadat de gebeurtenis-handlers van het onderdeel zijn aangeroepen. In sommige gevallen kan het onnodig of ongewenst zijn om een rerender te activeren nadat een gebeurtenis-handler is aangeroepen. Een gebeurtenis-handler kan bijvoorbeeld de onderdeelstatus niet wijzigen. In deze scenario's kan de app gebruikmaken van de IHandleEvent-interface om het gedrag van de gebeurtenisafhandeling van Blazorte beheren.

Opmerking

De benadering in deze sectie geleidt geen uitzonderingen door naar foutrandvoorwaarden. Zie voor meer informatie en demonstratiecode die foutgrenzen ondersteunt door ComponentBase.DispatchExceptionAsyncaan te roepen, AsNonRenderingEventHandler + ErrorBoundary = onverwacht gedrag (dotnet/aspnetcore #54543).

Als u rerenders voor alle gebeurtenis-handlers van een onderdeel wilt voorkomen, implementeert u IHandleEvent en geeft u een IHandleEvent.HandleEventAsync taak op die de gebeurtenis-handler aanroept zonder StateHasChangedaan te roepen.

In het volgende voorbeeld zorgt geen enkele aan het onderdeel toegevoegde evenementhandler ervoor dat er een rerender plaatsvindt, zodat HandleSelect geen rerender veroorzaakt wanneer het wordt aangeroepen.

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);
}

Naast het voorkomen van rerenders nadat gebeurtenishandlers globaal in een component worden geactiveerd, is het mogelijk om rerenders na een enkele gebeurtenishandler te voorkomen door de volgende hulpmethode te gebruiken.

Voeg de volgende EventUtil-klasse toe aan een Blazor-app. De statische acties en functies boven aan de klasse EventUtil bieden handlers die betrekking hebben op verschillende combinaties van argumenten en retourtypen die Blazor gebruikt bij het verwerken van gebeurtenissen.

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);
    }
}

Roep EventUtil.AsNonRenderingEventHandler aan om een gebeurtenishandler aan te roepen die geen render activeert wanneer deze wordt aangeroepen.

In het volgende voorbeeld:

  • Als u de eerste knop selecteert, die HandleClick1oproept, wordt een her-rendering geactiveerd.
  • Bij het selecteren van de tweede knop, die HandleClick2aanroept, wordt er geen opnieuw weergeven geactiveerd.
  • Door de derde knop te selecteren, die HandleClick3aanroept, wordt er geen nieuwe weergave geactiveerd en worden de gebeurtenisargumenten gebruikt (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);
    }
}

Naast het implementeren van de IHandleEvent-interface, kan het gebruik van de andere aanbevolen procedures die in dit artikel worden beschreven, ook helpen bij het verminderen van ongewenste renders nadat gebeurtenissen zijn verwerkt. Het overschrijven van ShouldRender in onderliggende componenten van de doelcomponent kan bijvoorbeeld worden gebruikt om het opnieuw renderen te beheren.

Vermijd het opnieuw maken van gedelegeerden voor veel herhaalde elementen of onderdelen

Blazor's recreatie van lambda-expressie delegeren voor elementen of onderdelen in een lus kan leiden tot slechte prestaties.

Het volgende component, dat wordt getoond in het artikel over gebeurtenisafhandeling, rendert een set knoppen. Elke knop wijst een gedelegeerde toe aan de bijbehorende @onclick gebeurtenis. Dit is prima als er niet veel knoppen zijn om weer te geven.

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}";
    }
}

Als een groot aantal knoppen wordt weergegeven met behulp van de voorgaande benadering, wordt de renderingsnelheid nadelig beïnvloed, wat leidt tot een slechte gebruikerservaring. Als u een groot aantal knoppen met een callback voor klikgebeurtenissen wilt weergeven, gebruikt het volgende voorbeeld een verzameling knopobjecten die de @onclick gedelegeerde van elke knop aan een Actiontoewijzen. De volgende aanpak vereist niet dat Blazor alle knopfuncties opnieuw maakt elke keer dat de knoppen worden weergegeven.

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 => { };
    }
}