Freigeben über


Bewährte Methoden zur Leistung des Renderings von ASP.NET Core Blazor

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 10-Version dieses Artikels.

Warnung

Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Informationen zum aktuellen Release finden Sie in der .NET 9-Version dieses Artikels.

Optimieren Sie die Renderinggeschwindigkeit, um die Renderingauslastung zu minimieren und die Reaktionsfähigkeit der Benutzeroberfläche zu verbessern, was zu einer zehnfachen oder höheren Verbesserung der Renderinggeschwindigkeit der Benutzeroberfläche führen kann.

Vermeiden Sie unnötiges Rendern von Komponententeilbäumen

Möglicherweise können Sie die meisten Renderingkosten einer übergeordneten Komponente reduzieren, indem Sie das erneute Rendern der Unterbäume der Kindkomponenten überspringen, wenn ein Ereignis auftritt. Sie sollten sich nur Gedanken darüber machen, das erneute Rendern von Unterstrukturen zu überspringen, die besonders teuer zu rendern sind und UI-Verzögerungen verursachen.

Zur Laufzeit sind Komponenten in einer Hierarchie vorhanden. Eine Stammkomponente (die erste geladene Komponente) verfügt über untergeordnete Komponenten. Im Gegenzug haben die untergeordneten Elemente der Wurzel ihre eigenen Kind-Elemente usw. Wenn ein Ereignis auftritt, z. B. ein Benutzer, der eine Schaltfläche auswählt, bestimmt der folgende Prozess, welche Komponenten erneut zurückverarbeitet werden sollen:

  1. Das Ereignis wird an die Komponente verteilt, die den Ereignishandler gerendert hat. Nach dem Ausführen des Ereignishandlers wird die Komponente erneut zurückgegeben.
  2. Wenn eine Komponente erneut zurückgegeben wird, stellt sie eine neue Kopie von Parameterwerten für die einzelnen untergeordneten Komponenten bereit.
  3. Nachdem eine neue Gruppe von Parameterwerten empfangen wurde, entscheidet, Blazor ob die Komponente erneut zurückgesetzt werden soll. Komponenten werden neu gerendert, wenn ShouldRendertrue zurückgibt, was das Standardverhalten ist, es sei denn, es wird überschrieben. Die Parameterwerte könnten sich geändert haben, zum Beispiel, wenn sie veränderliche Objekte sind.

Die letzten beiden Schritte der vorherigen Sequenz setzen die Komponentenhierarchie rekursiv nach unten fort. In vielen Fällen wird die gesamte Unterstruktur neu dargestellt. Ereignisse, die auf hochwertige Komponenten abzielen, können eine kostspielige Neudarstellung verursachen, da jede Komponente unter der hochwertigen Komponente neu gerendert werden muss.

Um eine Rekursion beim Rendern eines bestimmten Teilbaums zu verhindern, verwenden Sie eine der folgenden Ansätzen.

  • Stellen Sie sicher, dass untergeordnete Komponentenparameter bestimmte unveränderliche Typen aufweisen, wie z. B. string, int, bool und DateTime. Die integrierte Logik zum Erkennen von Änderungen überspringt automatisch die erneute Wiedergabe, wenn sich die unveränderlichen Parameterwerte nicht geändert haben. Wenn Sie eine untergeordnete Komponente mit <Customer CustomerId="item.CustomerId" /> rendern, wobei CustomerId ein int Typ ist, wird die Customer Komponente nicht erneut gerendert, es sei denn, item.CustomerId ändert sich.
  • Überschreiben ShouldRender, zurückgeben false:
    • Wenn Parameter nichtprimitive Typen oder nicht unterstützte unveränderliche Typen sind†, z. B. komplexe benutzerdefinierte Modelltypen oder RenderFragment Werte, und die Parameterwerte wurden nicht geändert,
    • Wenn Sie eine UI-Komponente erstellen, die sich nach dem anfänglichen Rendern nicht mehr ändert, egal wie sich die Parameterwerte ändern.

† Weitere Informationen finden Sie in der Änderungserkennungslogik in Blazorder Referenzquelle (ChangeDetection.cs).

Hinweis

Dokumentationslinks zur .NET-Referenzquelle laden in der Regel die Standard-Branch des Repositorys, die die aktuelle Entwicklung der nächsten Version von .NET darstellt. Um ein Tag für ein bestimmtes Release auszuwählen, wählen Sie diesen mit der Dropdownliste Switch branches or tags (Branches oder Tags wechseln) aus. Weitere Informationen finden Sie unter How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Auswählen eines Versionstags von ASP.NET Core-Quellcode (dotnet/AspNetCore.Docs #26205)).

Im folgenden Beispiel für das Flugsuche-Tool der Airline werden private Felder verwendet, um die erforderlichen Informationen nachzuverfolgen, um Änderungen zu erkennen. Der vorherige eingehende Flug-Bezeichner (prevInboundFlightId) und der vorherige ausgehende Flug-Bezeichner (prevOutboundFlightId) sammeln Informationen zum nächsten potenziellen Komponentenupdate. Wenn sich eine der Flugkennungen ändert, wenn die Parameter der Komponente in OnParametersSet festgelegt werden, wird die Komponente neu gerendert, weil shouldRender auf true gesetzt ist. Wenn shouldRender nach der Überprüfung der Flug-IDs auf false evaluiert, wird eine kostspielige Neu-Renderung vermieden:

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

Ein Ereignishandler kann auch shouldRender auf true setzen. Für die meisten Komponenten ist das Ermitteln der Erneuterenderung auf der Ebene einzelner Ereignishandler in der Regel nicht erforderlich.

Weitere Informationen finden Sie in den folgenden Ressourcen:

Virtualisierung

Beim Rendern großer Ui-Mengen in einer Schleife, z. B. einer Liste oder eines Rasters mit Tausenden von Einträgen, kann die schiere Menge der Renderingvorgänge zu einem Verzögerung beim Rendern der Benutzeroberfläche führen. Da der Benutzer nur eine kleine Anzahl von Elementen gleichzeitig sehen kann, ohne scrollen zu müssen, ist es häufig verschwendet, Zeit mit dem Rendern von Elementen zu verbringen, die derzeit nicht sichtbar sind.

Blazor bietet die Virtualize<TItem> Komponente an, um das Erscheinungsbild und das Scroll-Verhalten einer beliebig großen Liste zu gestalten, während nur die Listenelemente gerendert werden, die sich im aktuellen Scroll-Viewport befinden. Beispielsweise kann eine Komponente eine Liste mit 100.000 Einträgen rendern, aber nur die Renderingkosten von 20 Elementen bezahlen, die sichtbar sind.

Weitere Informationen finden Sie unter Razor-Komponentenvirtualisierung in ASP.NET Core.

Erstellen einfacher, optimierter Komponenten

Die meisten Razor Komponenten erfordern keine aggressiven Optimierungsbemühungen, da die meisten Komponenten nicht in der Benutzeroberfläche wiederholt werden und nicht mit hoher Häufigkeit erneut angezeigt werden. Routingfähige Komponenten mit einer @page Direktive und Komponenten, die zum Rendern von allgemeinen Ui-Elementen verwendet werden, z. B. Dialogfelder oder Formulare, werden wahrscheinlich nur jeweils einzeln angezeigt und nur als Reaktion auf eine Benutzergeste erneut gerendert. Diese Komponenten erzeugen normalerweise keine hohe Render-Auslastung, sodass Sie eine beliebige Kombination von Framework-Features frei verwenden können, ohne sich um die Renderleistung sorgen zu müssen.

Es gibt jedoch häufige Szenarien, in denen Komponenten im Maßstab wiederholt werden und häufig zu einer schlechten UI-Leistung führen:

  • Große geschachtelte Formulare mit Hunderten einzelner Elemente, z. B. Eingaben oder Beschriftungen.
  • Raster mit Hunderten von Zeilen oder Tausenden von Zellen.
  • Scatterplots mit Millionen von Datenpunkten.

Wenn jedes Element, jede Zelle oder jeder Datenpunkt als separate Komponenteninstanz modelliert wird, sind häufig so viele von ihnen vorhanden, dass ihre Renderingleistung kritisch wird. In diesem Abschnitt wird empfohlen, solche Komponenten einfach zu gestalten, damit die Benutzeroberfläche schnell und reaktionsfähig bleibt.

Vermeiden Sie Tausende von Komponenteninstanzen

Jede Komponente ist eine separate Insel, die unabhängig von ihren Eltern und Kindern rendern kann. Indem Sie auswählen, wie die Benutzeroberfläche in eine Hierarchie von Komponenten aufgeteilt werden soll, übernehmen Sie die Kontrolle über die Granularität des UI-Renderings. Dies kann zu einer guten oder schlechten Leistung führen.

Indem Sie die Benutzeroberfläche in separate Komponenten aufteilen, können Sie kleinere Teile der Benutzeroberfläche beim Auftreten von Ereignissen erneut ausführen. In einer Tabelle mit vielen Zeilen, die über eine Schaltfläche in jeder Zeile verfügen, können Sie möglicherweise nur diese einzelne Zeile neu rendern, indem Sie eine untergeordnete Komponente anstelle der gesamten Seite oder Tabelle verwenden. Jede Komponente erfordert jedoch zusätzlichen Arbeitsspeicher und CPU-Aufwand, um den unabhängigen Zustand und den Renderinglebenszyklus zu bewältigen.

Bei einem Test der ASP.NET Core Product Unit Engineers wurde ein Renderingaufwand von ca. 0,06 ms pro Komponenteninstanz in einer Blazor WebAssembly App angezeigt. Die Test-App hat eine einfache Komponente gerendert, die drei Parameter akzeptiert. Intern liegt der Aufwand größtenteils daran, den Zustand pro Komponente aus Wörterbüchern abzurufen und Parameter zu übergeben und zu empfangen. Durch Multiplikation lässt sich feststellen, dass das Hinzufügen von 2.000 zusätzlichen Komponenteninstanzen 0,12 Sekunden zur Renderzeit hinzufügt und die Benutzeroberfläche sich für Benutzer langsam anfühlen würde.

Es ist möglich, Komponenten einfacher zu machen, damit Sie mehr davon haben können. Eine leistungsfähigere Technik besteht jedoch häufig darin, zu vermeiden, dass so viele Komponenten gerendert werden. In den folgenden Abschnitten werden zwei Ansätze beschrieben, die Sie ausführen können.

Weitere Informationen zur Speicherverwaltung finden Sie unter "Verwalten des Speichers in bereitgestellten ASP.NET core serverseitigen Blazor Apps".

Untergeordnete Komponenten inline in ihre übergeordneten Komponenten einfügen: Betrachten Sie den folgenden Abschnitt einer übergeordneten Komponente, die in einer Schleife untergeordnete Komponenten rendert.

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

Das vorangehende Beispiel funktioniert gut, wenn Tausende von Nachrichten nicht gleichzeitig angezeigt werden. Wenn Sie Tausende von Nachrichten gleichzeitig anzeigen möchten, sollten Sie die separate Komponente ChatMessageDisplay ausgrenzen. Stattdessen betten Sie die untergeordnete Komponente in die übergeordnete Komponente ein. Mit dem folgenden Ansatz wird der Mehraufwand pro Komponente beim Rendern vermieden, wodurch der Overhead bei vielen untergeordneten Komponenten reduziert wird, allerdings auf Kosten der Möglichkeit, das Markup jeder untergeordneten Komponente unabhängig neu zu rendern.

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

Definieren Sie wiederverwendbare RenderFragments Komponenten im Code: Sie könnten untergeordnete Komponenten ausschließlich als Möglichkeit zur Wiederverwendung der Rendererlogik herauslösen. Wenn dies der Fall ist, können Sie wiederverwendbare Renderinglogik erstellen, ohne zusätzliche Komponenten zu implementieren. Definieren Sie im Block einer beliebigen Komponente @code eine RenderFragment. Rendern Sie das Fragment von einem beliebigen Ort so oft wie nötig:

@RenderWelcomeInfo

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

@RenderWelcomeInfo

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

Um Code für mehrere Komponenten wiederverwendbar zu machen RenderTreeBuilder , deklarieren Sie folgendes RenderFragmentpublic und static:

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

SayHello im vorherigen Beispiel kann aus einer nicht verknüpften Komponente aufgerufen werden. Diese Technik eignet sich zum Erstellen von Bibliotheken wiederverwendbarer Markupausschnitte, die ohne Komponentenaufwand gerendert werden.

RenderFragment Delegaten können Parameter akzeptieren. Die folgende Komponente übergibt die Nachricht (message) an die RenderFragment Stellvertretung:

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

Mit dem vorherigen Ansatz wird Rendering-Logik ohne zusätzlichen Aufwand pro Komponente wiederverwendet. Der Ansatz lässt jedoch keine unabhängige Aktualisierung der Unterstruktur der Benutzeroberfläche zu und hat auch nicht die Möglichkeit, das Rendern der Unterstruktur der Benutzeroberfläche zu überspringen, wenn das übergeordnete Element gerendert wird, da keine Komponentengrenze vorhanden ist. Die Zuweisung zu einem RenderFragment Delegat wird nur in Razor Komponentendateien (.razor) unterstützt.

Verwenden Sie für ein nicht statisches Feld, eine Methode oder eine Eigenschaft, auf die nicht von einem Feldinitialisierer verwiesen werden kann, z TitleTemplate . B. im folgenden Beispiel, eine Eigenschaft anstelle eines Felds für das RenderFragment:

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

Nicht zu viele Parameter annehmen

Wenn eine Komponente extrem häufig wiederholt wird, z. B. Hunderte oder Tausende von Malen, häuft sich der Mehraufwand für das Übergeben und Empfangen der einzelnen Parameter an.

Es ist selten, dass zu viele Parameter die Leistung stark einschränken, aber es kann ein Faktor sein. Bei einer TableCell Komponente, die innerhalb eines Rasters 4.000 Mal gerendert wird, fügt jeder an die Komponente übergebene Parameter rund 15 ms zu den Gesamtrenderingkosten hinzu. Das Übergeben von zehn Parametern erfordert ca. 150 ms und verursacht einen Verzögerungen beim Rendern der Benutzeroberfläche.

Um die Parameterlast zu reduzieren, bündeln Sie mehrere Parameter in einer benutzerdefinierten Klasse. Beispielsweise kann eine Tabellenzellenkomponente ein allgemeines Objekt akzeptieren. Im folgenden Beispiel ist Data für jede Zelle unterschiedlich, Options ist hingegen bei allen Zellinstanzen gleich.

@typeparam TItem

...

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

Beachten Sie jedoch, dass das Bündeln von Grundtypparametern in eine Klasse nicht immer ein Vorteil ist. Während die Parameteranzahl reduziert werden kann, wirkt sich dies auch auf das Verhalten der Änderungserkennung und des Renderings aus. Durch das Übergeben nicht-primitiver Parameter wird immer ein erneutes Rendern ausgelöst, da Blazor nicht wissen kann, ob beliebige Objekte intern veränderlich sind, während das Übergeben primitiver Parameter nur dann ein erneutes Rendern auslöst, wenn sich ihre Werte tatsächlich geändert haben.

Beachten Sie außerdem, dass es sich möglicherweise um eine Verbesserung handelt, die keine Tabellenzellenkomponente aufweist, wie im vorherigen Beispiel dargestellt, und stattdessen ihre Logik in die übergeordnete Komponente eingliedern.

Hinweis

Wenn mehrere Ansätze zur Verbesserung der Leistung verfügbar sind, ist ein Benchmarking der Ansätze in der Regel erforderlich, um zu bestimmen, welcher Ansatz die besten Ergebnisse liefert.

Weitere Informationen zu generischen Typparametern (@typeparam) finden Sie in den folgenden Ressourcen:

Sicherstellen, dass kaskadierende Parameter behoben sind

Die CascadingValue Komponente verfügt über einen optionalen IsFixed Parameter:

  • Wenn IsFixed (standard) ist false , richtet jeder Empfänger des kaskadierten Werts ein Abonnement für den Empfang von Änderungsbenachrichtigungen ein. Jedes [CascadingParameter] ist wesentlich teurer als ein reguläres [Parameter] aufgrund der Abonnementverfolgung.
  • Wenn IsFixedtrue ist (z. B. <CascadingValue Value="someValue" IsFixed="true">), erhalten die Empfänger den Anfangswert, richten jedoch kein Abonnement ein, um Updates zu erhalten. Jedes [CascadingParameter] ist leicht und nicht teurer als eine normale [Parameter].

Durch das Setzen von IsFixed auf true wird die Leistung verbessert, wenn eine große Anzahl anderer Komponenten vorhanden ist, die den kaskadierten Wert erhalten. Setzen Sie nach Möglichkeit IsFixed auf true bei kaskadierten Werten. Sie können IsFixed auf true setzen, wenn sich der angegebene Wert im Laufe der Zeit nicht ändert.

Wenn eine Komponente this als kaskadierten Wert übergibt, kann IsFixed auch auf true festgelegt werden, da sich this während des Lebenszyklus der Komponente nie ändert.

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

Weitere Informationen finden Sie unter Kaskadierende Werte und Parameter in Blazor in ASP.NET Core.

Vermeiden von Attributsplizieren mit CaptureUnmatchedValues

Komponenten können wählen, Parameterwerte als "ungleich" mit dem CaptureUnmatchedValues-Flag zu empfangen.

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

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

Dieser Ansatz ermöglicht das Übergeben beliebiger zusätzlicher Attribute an das Element. Dieser Ansatz ist jedoch teuer, da der Renderer:

  • Stimmen Sie alle bereitgestellten Parameter mit dem Satz bekannter Parameter überein, um ein Wörterbuch zu erstellen.
  • Verfolgen Sie, wie mehrere Kopien desselben Attributs einander überschreiben.

Verwenden Sie CaptureUnmatchedValues , wo die Leistung von Komponentenrendering nicht kritisch ist, z. B. Komponenten, die nicht häufig wiederholt werden. Für Komponenten, die im Maßstab gerendert werden, z. B. jedes Element in einer großen Liste oder in den Zellen eines Rasters, versuchen Sie, attributsplizieren zu vermeiden.

Weitere Informationen finden Sie unter ASP.NET Core Blazor-Attributsplatting und beliebige Parameter.

Implementieren Sie SetParametersAsync manuell

Ein erheblicher Aufwand für das Rendern pro Komponente besteht darin, eingehende Parameterwerte zu [Parameter]-Eigenschaften zu schreiben. Der Renderer verwendet Reflexion, um die Parameterwerte zu schreiben, was bei umfangreichem Einsatz zu einer schlechten Leistung führen kann.

In manchen Extremfällen möchten Sie die Reflexion vermeiden und Ihre eigene Parametereinstellungslogik manuell implementieren. Dies kann anwendbar sein, wenn:

  • Eine Komponente wird extrem häufig gerendert, beispielsweise wenn hunderte oder tausende Kopien der Komponente auf der Benutzeroberfläche vorhanden sind.
  • Eine Komponente akzeptiert viele Parameter.
  • Sie stellen fest, dass der Mehraufwand für empfangende Parameter eine erkennbare Auswirkung auf die Reaktionsfähigkeit der Benutzeroberfläche hat.

In extremen Fällen können Sie die virtuelle SetParametersAsync Methode der Komponente überschreiben und ihre eigene komponentenspezifische Logik implementieren. Im folgenden Beispiel werden Wörterbuchsuchvorgänge absichtlich vermieden:

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

Im vorherigen Code führt das Zurückgeben der Basisklasse SetParametersAsync die normale Lebenszyklusmethode aus, ohne Parameter erneut zuzuweisen.

Wie Sie im vorherigen Code sehen können, ist das Überschreiben SetParametersAsync und Bereitstellen benutzerdefinierter Logik kompliziert und mühsam, daher empfehlen wir im Allgemeinen nicht, diesen Ansatz zu übernehmen. In extremen Fällen kann es die Renderingleistung um 20-25%verbessern. Sie sollten diesen Ansatz jedoch nur in den weiter oben in diesem Abschnitt aufgeführten Extremszenarien berücksichtigen.

Ereignisse nicht zu schnell auslösen

Einige Browserereignisse werden extrem häufig ausgelöst. Beispielsweise können onmousemove und onscroll zehn- oder hundertmal pro Sekunde feuern. In den meisten Fällen müssen Sie die UI-Aktualisierungen nicht häufig ausführen. Wenn Ereignisse zu schnell ausgelöst werden, können Sie die Reaktionsfähigkeit der Benutzeroberfläche beeinträchtigen oder übermäßige CPU-Zeit verbrauchen.

Anstatt native Ereignisse zu verwenden, die schnell feuern, sollten Sie die Nutzung von JS Interop in Betracht ziehen, um einen Callback zu registrieren, der weniger häufig feuert. Die folgende Komponente zeigt beispielsweise die Position der Maus an, aktualisiert jedoch nur einmal alle 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();
}

Der entsprechende JavaScript-Code registriert den DOM-Ereignislistener für Mausbewegungen. In diesem Beispiel verwendet der Ereignislistener die Lodash-Funktionthrottle, um die Häufigkeit der Aufrufe zu begrenzen:

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

Vermeiden einer erneuten Wiedergabe nach der Behandlung von Ereignissen ohne Zustandsänderungen

Komponenten erben von ComponentBase, die automatisch StateHasChanged aufruft, nachdem die Ereignis-Handler der Komponente aufgerufen wurden. In einigen Fällen kann es unnötig oder unerwünscht sein, einen Erneuterender auszulösen, nachdem ein Ereignishandler aufgerufen wurde. Beispielsweise kann ein Ereignishandler den Komponentenstatus nicht ändern. In diesen Szenarien kann die App die IHandleEvent Schnittstelle nutzen, um das Verhalten der BlazorEreignisbehandlung zu steuern.

Hinweis

Der Ansatz in diesem Abschnitt bezieht sich nicht auf Fehlerbegrenzungen. Weitere Informationen und Demo-Code, der Fehlerbegrenzungen durch Aufrufen von ComponentBase.DispatchExceptionAsync unterstützt, finden Sie unter AsNonRenderingEventHandler + ErrorBoundary = unerwartetes Verhalten (dotnet/aspnetcore #54543).

Um Neurendern zu verhindern, implementieren Sie IHandleEvent und stellen Sie eine IHandleEvent.HandleEventAsync-Aufgabe bereit, die den Ereignishandler aufruft, ohne StateHasChanged zu verwenden.

Im folgenden Beispiel wird der Komponente kein Ereignishandler hinzugefügt, der ein erneutes Rendern auslöst. Daher führt der Aufruf von HandleSelect nicht zu einem erneuten Rendern.

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

Zusätzlich zur Vermeidung von Neurenderings nach der Auslösung von Event-Handlern in einer Komponente auf globale Weise ist es möglich, Neurenderings nach einem einzelnen Event-Handler mithilfe der folgenden Hilfsmethode zu vermeiden.

Fügen Sie einer EventUtil App die folgende Blazor Klasse hinzu. Die statischen Aktionen und Funktionen oben in der EventUtil Klasse stellen Handler bereit, die mehrere Kombinationen von Argumenten und Rückgabetypen abdecken, die Blazor beim Behandeln von Ereignissen verwendet werden.

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

Rufen Sie EventUtil.AsNonRenderingEventHandler auf, um einen Ereignishandler aufzurufen, der beim Aufrufen kein Rendern auslöst.

Im folgenden Beispiel:

  • Wenn Sie die erste Schaltfläche auswählen, die aufruft HandleClick1, wird eine erneute Wiedergabe ausgelöst.
  • Wenn Sie die zweite Schaltfläche auswählen, die HandleClick2 aufruft, führt das nicht zu einer Neudarstellung.
  • Wenn Sie die dritte Schaltfläche auswählen, die HandleClick3 aufruft, wird keine Neurenderung ausgelöst, und es werden Ereignisargumente (MouseEventArgs) verwendet.

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

Zusätzlich zur Implementierung der IHandleEvent Schnittstelle kann die Nutzung der anderen in diesem Artikel beschriebenen bewährten Methoden auch dazu beitragen, unerwünschte Renderungen zu reduzieren, nachdem Ereignisse behandelt wurden. Beispielsweise kann das Überschreiben ShouldRender in untergeordneten Komponenten der Zielkomponente verwendet werden, um die Erneuterenderung zu steuern.

Vermeiden Sie das Neuerstellen von Delegaten für viele wiederholte Elemente oder Komponenten.

BlazorDie Rekonstruktion von Lambda-Ausdrucksdelegaten für Elemente oder Komponenten in einer Schleife kann zu einer schlechten Leistung führen.

Die folgende Komponente, die im Artikel zur Ereignisbehandlung gezeigt wird, rendert eine Reihe von Schaltflächen. Jede Schaltfläche weist ihrem @onclick Ereignis eine Stellvertretung zu. Dies ist in Ordnung, wenn nicht viele Schaltflächen gerendert werden müssen.

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

Wenn eine große Anzahl von Schaltflächen mithilfe des vorherigen Ansatzes gerendert wird, wird die Renderinggeschwindigkeit beeinträchtigt, was zu einer schlechten Benutzererfahrung führt. Um eine große Anzahl von Schaltflächen mit einem Callback für Klickereignisse zu rendern, verwendet das folgende Beispiel eine Auflistung von Schaltflächenobjekten, die jedem Schaltflächen-Delegate @onclick eine Action zuweisen. Der folgende Ansatz erfordert Blazor nicht, dass alle Schaltflächendelegaten jedes Mal neu erstellt werden, wenn die Schaltflächen gerendert werden.

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