bästa praxis för ASP.NET Core Blazor-prestanda
Not
Det här är inte den senaste versionen av den här artikeln. Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Varning
Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. Den aktuella versionen finns i .NET 9-versionen av denna artikel.
Viktig
Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.
Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Blazor är optimerad för höga prestanda i de flesta realistiska scenarier för programgränssnittet. Den bästa prestandan beror dock på att utvecklare använder rätt mönster och funktioner.
Notera
Kodexemplen i den här artikeln använder nullbara referenstyper (NRT) och .NET-kompilatorn null-state static analysis, som stöds i ASP.NET Core i .NET 6 eller senare.
Optimera återgivningshastigheten
Optimera återgivningshastigheten för att minimera renderingsarbetsbelastningen och förbättra användargränssnittsresponsiviteten, vilket kan ge en tiofaldig eller högre förbättring i UI-återgivningshastigheten.
Undvik onödig rendering av komponentunderträd
Du kanske kan ta bort största delen av renderingskostnaden för en överordnad komponent genom att undvika återrenderingen av de underordnade elementen när en händelse inträffar. Du bör endast vara orolig för att undvika att rendera om underträd som är särskilt kostsamma att rendera och orsakar användargränssnittslagg.
Vid körning är komponenter organiserade i en hierarki. En rotkomponent (den första komponenten som lästs in) har delkomponenter. Rotens barn har i sin tur sina egna barnkomponenter och så vidare. När en händelse inträffar, till exempel när en användare väljer en knapp, avgör följande process vilka komponenter som ska återskapas:
- Händelsen skickas till komponenten som renderade händelsens hanterare. När händelsehanteraren har körts återskapas komponenten.
- När en komponent återskapas, tillhandahåller den en ny kopia av parametervärdena till var och en av dess underordnade komponenter.
- När en ny uppsättning parametervärden har tagits emot avgör varje komponent om de ska återskapas. Komponenterna återskapas om parametervärdena kan ha ändrats, till exempel om de är föränderliga objekt.
De två sista stegen i föregående sekvens fortsätter rekursivt nedåt i komponenthierarkin. I många fall renderas hela underträdet om. Händelser som riktar sig till komponenter på hög nivå kan orsaka dyr rerendering eftersom varje komponent under komponenten på hög nivå måste återskapas.
Om du vill förhindra återrekursion till ett visst underträd använder du någon av följande metoder:
- Se till att parametrar för barnkomponenter är av primitiva oföränderliga typer, som till exempel
string
,int
,bool
,DateTime
och andra liknande typer. Den inbyggda logiken för att identifiera ändringar hoppar automatiskt över rerendering om de primitiva oföränderliga parametervärdena inte har ändrats. Om en underordnad komponent renderas med<Customer CustomerId="item.CustomerId" />
, därCustomerId
är av typenint
, så renderas inteCustomer
-komponenten om inteitem.CustomerId
förändras. - Åsidosätt ShouldRender:
- Att acceptera värden för icke-primitiva parametrar, till exempel komplexa anpassade modelltyper, händelseåterrop eller värden av typen RenderFragment.
- Om du redigerar en komponent med endast användargränssnitt som inte ändras efter den första återgivningen, oavsett parametervärdeändringar.
I följande exempel på flygsökningsverktyget används privata fält för att spåra nödvändig information för att identifiera ändringar. Den tidigare inkommande flygidentifieraren (prevInboundFlightId
) och den tidigare utgående flygidentifieraren (prevOutboundFlightId
) spårar information för nästa potentiella komponentuppdatering. Om någon av flygidentifierarna ändras när komponentens parametrar anges i OnParametersSet
, hämtas komponenten igen eftersom shouldRender
är inställd på true
. Om shouldRender
utvärderas till false
efter kontroll av flygidentifierarna undviks en dyr rerender:
@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;
}
En händelsehanterare kan också ange shouldRender
till true
. För de flesta komponenter är det vanligtvis inte nödvändigt att bestämma omrendering på nivån för enskilda händelsehanterare.
Mer information finns i följande resurser:
Virtualisering
När du återger stora mängder användargränssnitt i en loop, till exempel en lista eller ett rutnät med tusentals poster, kan den stora mängden renderingsåtgärder leda till en fördröjning i användargränssnittsrendering. Med tanke på att användaren bara kan se ett litet antal element samtidigt utan att rulla, är det ofta slöseri att ägna tid åt att återge element som för närvarande inte är synliga.
Blazor tillhandahåller den Virtualize<TItem>-komponenten för att skapa utseendet och rullningsbeteendena för en godtyckligt stor lista samtidigt som endast de listobjekt som finns i den aktuella rullningsvyporten renderas. En komponent kan till exempel återge en lista med 100 000 poster men bara betala renderingskostnaden för 20 objekt som är synliga.
Mer information finns i ASP.NET Core Razor-komponentvirtualisering.
Skapa enkla, optimerade komponenter
De flesta Razor komponenter kräver inte aggressiva optimeringsåtgärder eftersom de flesta komponenter inte upprepas i användargränssnittet och inte rerenderar med hög frekvens. Routbara komponenter med ett @page
-direktiv och komponenter som används för att återge delar av användargränssnittet på hög nivå, till exempel dialogrutor eller formulär, visas troligen bara en i taget och endast återskapas som svar på en användargest. Dessa komponenter skapar vanligtvis inte hög återgivningsbelastning, så du kan fritt använda valfri kombination av ramverkets funktioner utan att behöva oroa dig för prestandan.
Det finns dock vanliga scenarier där komponenter upprepas i stor skala och ofta resulterar i dåliga användargränssnittsprestanda:
- Stora kapslade formulär med hundratals enskilda element, till exempel indata eller etiketter.
- Rutnät med hundratals rader eller tusentals celler.
- Punktdiagram med miljontals datapunkter.
Om du modellerar varje element, cell eller datapunkt som en separat komponentinstans finns det ofta så många av dem att deras renderingsprestanda blir kritisk. Det här avsnittet innehåller råd om hur du gör sådana komponenter lätta så att användargränssnittet förblir snabbt och dynamiskt.
Undvik tusentals komponentinstanser
Varje komponent är en separat ö som kan återges oberoende av sina föräldrar och barn. Genom att välja hur användargränssnittet ska delas upp i en hierarki med komponenter tar du kontroll över detaljrikedomen för UI-återgivning. Detta kan resultera i antingen bra eller dåliga prestanda.
Genom att dela upp användargränssnittet i separata komponenter kan mindre delar av användargränssnittet rendreras om när händelser inträffar. I en tabell med många rader som har en knapp i varje rad kanske du bara kan ha den enda radrerendern med hjälp av en underordnad komponent i stället för hela sidan eller tabellen. Varje komponent kräver dock ytterligare minne och processorkostnader för att hantera dess oberoende tillstånd och återgivningslivscykel.
I ett test som utfördes av ASP.NET Core-produktenhetstekniker sågs en renderingskostnad på cirka 0,06 ms per komponentinstans i en Blazor WebAssembly app. Testappen renderade en enkel komponent som accepterar tre parametrar. Internt beror omkostnaderna till stor del på att tillståndet per komponent hämtas från ordlistor och att parametrar skickas och tas emot. Med multiplikation kan du se att lägga till 2 000 extra komponentinstanser skulle lägga till 0,12 sekunder till återgivningstiden och att användargränssnittet skulle börja kännas långsamt för användarna.
Det är möjligt att göra komponenterna enklare så att du kan få fler av dem. En mer kraftfull teknik är dock ofta att undvika att ha så många komponenter att rendera. I följande avsnitt beskrivs två metoder som du kan använda.
Mer information om minneshantering finns i Värd och distribuera ASP.NET Core-Blazor-appar på serversidan.
Infoga barnkomponenter i sina föräldrar
Överväg följande del av en överordnad komponent som renderar underordnade komponenter i en 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; }
}
Föregående exempel fungerar bra om tusentals meddelanden inte visas samtidigt. För att visa tusentals meddelanden samtidigt, överväg i stället för att bortse från den separata ChatMessageDisplay
-komponenten. Infoga i stället den underordnade komponenten i den överordnade komponenten. Följande metod undviker omkostnaderna per komponent för att återge så många underordnade komponenter på bekostnad av att förlora möjligheten att återskapa varje underordnad komponents markering oberoende av varandra:
<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>
Definiera återanvändbara RenderFragments
i kod
Kanske bryter du ut barnkomponenter bara för att återanvända renderingslogik. I så fall kan du skapa återanvändbar renderingslogik utan att implementera ytterligare komponenter. I varje komponents @code
-block definierar du en RenderFragment. Rendera fragmentet från valfri plats så många gånger som det behövs:
@RenderWelcomeInfo
<p>Render the welcome content a second time:</p>
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!</p>;
}
Om du vill göra RenderTreeBuilder kod återanvändbar för flera komponenter deklarerar du RenderFragmentpublic
och static
:
public static RenderFragment SayHello = @<h1>Hello!</h1>;
SayHello
i föregående exempel kan anropas från en orelaterad komponent. Den här tekniken är användbar för att skapa bibliotek med återanvändbara markeringsfragment som renderas utan omkostnader per komponent.
RenderFragment ombud kan acceptera parametrar. Följande komponent skickar meddelandet (message
) till RenderFragment ombudet:
<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>;
}
Föregående metod återanvänder renderingslogik utan omkostnader per komponent. Metoden tillåter dock inte uppdatering av underträdet i användargränssnittet oberoende av varandra, och den har inte heller möjlighet att hoppa över återgivningen av underträdet i användargränssnittet när dess överordnade renderas eftersom det inte finns någon komponentgräns. Tilldelning till en RenderFragment delegat stöds endast i Razor komponentfiler (.razor
).
För ett icke-statiskt fält, en metod eller en egenskap som inte kan refereras till av en fältinitierare, till exempel TitleTemplate
i följande exempel, använder du en egenskap i stället för ett fält för RenderFragment:
protected RenderFragment DisplayTitle =>
@<div>
@TitleTemplate
</div>;
Ta inte emot för många parametrar
Om en komponent upprepas mycket ofta, till exempel hundratals eller tusentals gånger, byggs omkostnaderna för att skicka och ta emot varje parameter upp.
Det är ovanligt att för många parametrar kraftigt begränsar prestanda, men det kan vara en faktor. För en TableCell
komponent som renderar 4 000 gånger i ett rutnät lägger varje parameter som skickas till komponenten till cirka 15 ms i den totala renderingskostnaden. Att skicka tio parametrar kräver cirka 150 ms och orsakar en UI-återgivningsfördröjning.
Om du vill minska parameterbelastningen paketar du flera parametrar i en anpassad klass. En tabellcellskomponent kan till exempel acceptera ett gemensamt objekt. I följande exempel är Data
olika för varje cell, men Options
är vanligt för alla cellinstanser:
@typeparam TItem
...
@code {
[Parameter]
public TItem? Data { get; set; }
[Parameter]
public GridOptions? Options { get; set; }
}
Kom dock ihåg att paketering av primitiva parametrar i en klass inte alltid är en fördel. Även om det kan minska antalet parametrar påverkar det också hur ändringsidentifiering och återgivning fungerar. Att skicka icke-primitiva parametrar utlöser alltid en återåtergivning, eftersom Blazor inte kan veta om godtyckliga objekt har internt föränderligt tillstånd, medan överföring av primitiva parametrar bara utlöser en återåtergivning om deras värden faktiskt har ändrats.
Tänk också på att det kan vara en förbättring att inte ha en tabellcellskomponent, som du ser i föregående exempel, och i stället infoga logiken i den överordnade komponenten.
Notera
När flera metoder är tillgängliga för att förbättra prestandan krävs vanligtvis benchmarking av metoderna för att avgöra vilken metod som ger bäst resultat.
Mer information om generiska typparametrar (@typeparam
) finns i följande resurser:
- Razor syntaxreferens för ASP.NET Core
- ASP.NET Core Razor komponenter
- ASP.NET Core Blazor mallade komponenter
Se till att sammanhängande parametrar är fasta
Komponenten CascadingValue
har en valfri parameter IsFixed
:
- Om
IsFixed
ärfalse
(standard) konfigurerar varje mottagare av det överlappande värdet en prenumeration för att ta emot ändringsmeddelanden. Varje[CascadingParameter]
är betydligt dyrare än en vanlig[Parameter]
på grund av prenumerationsspårningen. - Om
IsFixed
ärtrue
(till exempel<CascadingValue Value="someValue" IsFixed="true">
) får mottagarna det ursprungliga värdet men konfigurerar inte en prenumeration för att ta emot uppdateringar. Varje[CascadingParameter]
är lätt och inte dyrare än en vanlig[Parameter]
.
Om du anger IsFixed
till true
förbättras prestandan om det finns ett stort antal andra komponenter som tar emot det överlappande värdet. Ange om möjligt IsFixed
till true
på överlappande värden. Du kan ange IsFixed
till true
när det angivna värdet inte ändras över tid.
När en komponent skickar this
som ett överlappande värde kan IsFixed
också anges till true
eftersom this
aldrig ändras under komponentens livscykel:
<CascadingValue Value="this" IsFixed="true">
<SomeOtherComponents>
</CascadingValue>
Mer information finns i ASP.NET Core Blazor sammanhängande värden och parametrar.
Undvik attributsplätering med CaptureUnmatchedValues
Komponenter kan välja att ta emot "omatchade" parametervärden med hjälp av flaggan CaptureUnmatchedValues:
<div @attributes="OtherAttributes">...</div>
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? OtherAttributes { get; set; }
}
Med den här metoden kan du skicka godtyckliga ytterligare attribut till elementet. Den här metoden är dock dyr eftersom renderaren måste:
- Matcha alla angivna parametrar mot uppsättningen kända parametrar för att skapa en ordlista.
- Håll reda på hur flera kopior av samma attribut skriver över varandra.
Använd CaptureUnmatchedValues där komponentrenderingsprestanda inte är kritiska, till exempel komponenter som inte upprepas ofta. För komponenter som renderas i stor skala, till exempel varje objekt i en stor lista eller i cellerna i ett rutnät, försöker du undvika attributsplattning.
Mer information finns i ASP.NET Core Blazor attribute splatting och godtyckliga parametrar.
Implementera SetParametersAsync
manuellt
En betydande källa till omkostnader för rendering per komponent är att skriva de inkommande parametervärdena till [Parameter]
-egenskaper. Renderaren använder reflektion för att skriva parametervärdena, vilket kan leda till dåliga prestanda i stor skala.
I vissa extrema fall kanske du vill undvika reflektionen och implementera din egen parameterinställningslogik manuellt. Detta kan vara tillämpligt när:
- En komponent återges mycket ofta, till exempel när det finns hundratals eller tusentals kopior av komponenten i användargränssnittet.
- En komponent accepterar många parametrar.
- Du upptäcker att omkostnaderna för att ta emot parametrar har en observerbar inverkan på UI-svarstiden.
I extrema fall kan du åsidosätta komponentens virtuella SetParametersAsync-metod och implementera din egen komponentspecifika logik. I följande exempel undviks avsiktligt ordlistesökningar:
@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);
}
}
I den föregående koden, genom att returnera basklassen SetParametersAsync, kommer den normala livscykelmetoden att köras utan att parametrarna tilldelas igen.
Som du ser i föregående kod är det komplicerat och besvärligt att åsidosätta SetParametersAsync och tillhandahålla anpassad logik, så vi rekommenderar vanligtvis inte att du använder den här metoden. I extrema fall kan det förbättra renderingsprestanda med 20–25%, men du bör bara överväga den här metoden i de extrema scenarier som anges tidigare i det här avsnittet.
Utlös inte händelser för snabbt
Vissa webbläsarhändelser utlöses väldigt ofta. Till exempel kan onmousemove
och onscroll
utlösas tiotals eller hundratals gånger per sekund. I de flesta fall behöver du inte utföra användargränssnittsuppdateringar ofta. Om händelser utlöses för snabbt kan du skada UI-svarstiden eller förbruka för hög CPU-tid.
I stället för att använda inbyggda händelser som snabbt utlöses bör du överväga att använda JS interop för att registrera ett återanrop som utlöses mindre ofta. Följande komponent visar till exempel musens position men uppdateras bara högst en gång var 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();
}
Motsvarande JavaScript-kod registrerar DOM-händelselyssnaren för musrörelse. I det här exemplet använder händelselyssnaren Lodashs throttle
funktion för att begränsa antalet anrop:
<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>
Undvik att rendera om efter att ha hanterat händelser utan ändringar av tillståndet
Komponenter ärver från ComponentBase, som automatiskt anropar StateHasChanged efter att komponentens händelsehanterare har anropats. I vissa fall kan det vara onödigt eller oönskat att utlösa en rerender när en händelsehanterare anropas. En händelsehanterare kanske till exempel inte ändrar komponenttillståndet. I dessa scenarier kan appen använda IHandleEvent-gränssnittet för att styra beteendet för Blazorhändelsehantering.
Obs
Metoden i det här avsnittet hanterar inte undantag inom felgränser. För mer information och demonstrationskod som stöder felgränser genom att anropa ComponentBase.DispatchExceptionAsync, se AsNonRenderingEventHandler + ErrorBoundary = oväntat beteende (dotnet/aspnetcore
#54543).
För att förhindra omritningar för alla händelsehanterare i en komponent, implementera IHandleEvent och tillhandahåll ett IHandleEvent.HandleEventAsync-uppdrag som anropar händelsehanteraren utan att anropa StateHasChanged.
I följande exempel utlöser ingen händelsehanterare som läggs till komponenten en rerender, så HandleSelect
resulterar inte i en rerender när den anropas.
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);
}
Förutom att förhindra omrenderingar efter att en händelsehanterare har körts globalt, är det möjligt att förhindra omrenderingar efter en enskild händelsehanterare genom att använda följande metod.
Lägg till följande EventUtil
-klass i en Blazor-app. Statiska åtgärder och funktioner överst i klassen EventUtil
tillhandahåller hanterare som omfattar flera kombinationer av argument och returtyper som Blazor använder vid hantering av händelser.
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);
}
}
Anropa EventUtil.AsNonRenderingEventHandler
för att anropa en händelsehanterare som inte utlöser en återgivning när den anropas.
I följande exempel:
- Om du väljer den första knappen, som anropar
HandleClick1
, utlöses en rerender. - Att välja den andra knappen, som triggar
HandleClick2
, initierar ingen omrendering. - Om du väljer den tredje knappen, som anropar
HandleClick3
, utlöses inte en rerender och använder händelseargument (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);
}
}
Förutom att implementera IHandleEvent-gränssnittet kan användning av andra metodtips som beskrivs i den här artikeln också bidra till att minska oönskade återgivningar när händelser har hanterats. Till exempel kan att åsidosätta ShouldRender i målkomponentens underordnade komponenter användas för att styra omrendering.
Undvik att återskapa ombud för många upprepade element eller komponenter
Blazorrekreation av lambda uttryck delegerar för element eller komponenter i en loop kan leda till dålig prestanda.
Följande komponent som visas i artikeln händelsehantering renderar en uppsättning knappar. Varje knapp tilldelar en delegerad till sin @onclick
-händelse, vilket är bra om det inte finns många knappar att rendera.
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}";
}
}
Om ett stort antal knappar återges med föregående metod påverkas återgivningshastigheten negativt, vilket leder till en dålig användarupplevelse. För att rendera ett stort antal knappar med ett återanrop för klickhändelser, använder följande exempel en samling av knappobjekt som tilldelar varje knapps @onclick
-delegat till dess Action-delegat. Följande metod kräver inte Blazor för att återskapa alla knappdelegater varje gång knapparna återges:
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 => { };
}
}
Optimera JavaScript-interophastighet
Anrop mellan .NET och JavaScript kräver ytterligare omkostnader eftersom:
- Anrop är asynkrona.
- Parametrar och returvärden är JSON-serialiserade för att ge en lättförstårlig konverteringsmekanism mellan .NET- och JavaScript-typer.
Dessutom skickas dessa anrop över nätverket för Blazor appar på serversidan.
Undvik alltför detaljerade samtal
Eftersom varje anrop medför vissa omkostnader kan det vara värdefullt att minska antalet anrop. Tänk på följande kod som lagrar en samling objekt i webbläsarens localStorage
:
private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
foreach (var item in items)
{
await JS.InvokeVoidAsync("localStorage.setItem", item.Id,
JsonSerializer.Serialize(item));
}
}
I föregående exempel görs ett separat JS interop-anrop för varje objekt. I stället minskar följande metod JS interop till ett enda anrop:
private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
await JS.InvokeVoidAsync("storeAllInLocalStorage", items);
}
Motsvarande JavaScript-funktion lagrar hela samlingen med objekt på klienten:
function storeAllInLocalStorage(items) {
items.forEach(item => {
localStorage.setItem(item.id, JSON.stringify(item));
});
}
För Blazor WebAssembly appar förbättras prestanda avsevärt genom att slå samman enskilda JS interop-anrop till ett enda anrop vanligtvis endast om komponenten gör ett stort antal JS interop-anrop.
Överväg att använda synkrona anrop
Anropa JavaScript från .NET
Det här avsnittet gäller endast komponenter på klientsidan.
JS interop-anrop är asynkrona, oavsett om den anropade koden är synkron eller asynkron. Anrop är asynkrona för att säkerställa att komponenterna är kompatibla i återgivningslägen på serversidan och på klientsidan. På servern måste alla JS interop-anrop vara asynkrona eftersom de skickas via en nätverksanslutning.
Om du vet att komponenten bara körs på WebAssembly kan du välja att göra synkrona JS interop-anrop. Detta har något mindre omkostnader än att göra asynkrona anrop och kan resultera i färre återgivningscykler eftersom det inte finns något mellanliggande tillstånd i väntan på resultat.
Om du vill göra ett synkront anrop från .NET till JavaScript i en komponent på klientsidan, konvertera IJSRuntime till IJSInProcessRuntime för att göra interop-anropet JS:
@inject IJSRuntime JS
...
@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
}
}
När du arbetar med IJSObjectReference i ASP.NET Core 5.0 eller senare komponenter på klientsidan kan du använda IJSInProcessObjectReference synkront i stället. IJSInProcessObjectReference implementerar IAsyncDisposable/IDisposable och ska tas bort för skräpinsamling för att förhindra en minnesläcka, vilket visas i följande exempel:
@inject IJSRuntime JS
@implements IDisposable
...
@code {
...
private IJSInProcessObjectReference? module;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var jsInProcess = (IJSInProcessRuntime)JS;
module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import",
"./scripts.js");
var value = module.Invoke<string>("javascriptFunctionIdentifier");
}
}
...
void IDisposable.Dispose()
{
if (module is not null)
{
await module.Dispose();
}
}
}
I föregående exempel fångas en JSDisconnectedException inte under modulavveckling eftersom det inte finns någon Blazor–SignalR-krets i en Blazor WebAssembly-app som går förlorad. Mer information finns i ASP.NET Core Blazor JavaScript-samverkan (JS interop).
Anropa .NET från JavaScript
Det här avsnittet gäller endast komponenter på klientsidan.
JS interop-anrop är asynkrona, oavsett om den anropade koden är synkron eller asynkron. Anrop är asynkrona för att säkerställa att komponenterna är kompatibla i återgivningslägen på serversidan och på klientsidan. På servern måste alla JS interop-anrop vara asynkrona eftersom de skickas via en nätverksanslutning.
Om du vet att komponenten bara körs på WebAssembly kan du välja att göra synkrona JS interop-anrop. Detta har något mindre omkostnader än att göra asynkrona anrop och kan resultera i färre återgivningscykler eftersom det inte finns något mellanliggande tillstånd i väntan på resultat.
Om du vill göra ett synkront anrop från JavaScript till .NET i en komponent på klientsidan använder du DotNet.invokeMethod
i stället för DotNet.invokeMethodAsync
.
Synkrona anrop fungerar om:
Det här avsnittet gäller endast komponenter på klientsidan.
JS interop-anrop är asynkrona, oavsett om den anropade koden är synkron eller asynkron. Anrop är asynkrona för att säkerställa att komponenterna är kompatibla i återgivningslägen på serversidan och på klientsidan. På servern måste alla JS interop-anrop vara asynkrona eftersom de skickas via en nätverksanslutning.
Om du vet att komponenten bara körs på WebAssembly kan du välja att göra synkrona JS interop-anrop. Detta har något mindre omkostnader än att göra asynkrona anrop och kan resultera i färre återgivningscykler eftersom det inte finns något mellanliggande tillstånd i väntan på resultat.
Om du vill göra ett synkront anrop från .NET till JavaScript, i en komponent på klientsidan, kastar du IJSRuntime till IJSInProcessRuntime för att kunna göra ett JS interop-anrop.
@inject IJSRuntime JS
...
@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
}
}
När du arbetar med IJSObjectReference i ASP.NET Core 5.0 eller senare komponenter på klientsidan kan du använda IJSInProcessObjectReference synkront i stället. IJSInProcessObjectReference implementerar IAsyncDisposable/IDisposable och ska tas bort för skräpinsamling för att förhindra en minnesläcka, vilket visas i följande exempel:
@inject IJSRuntime JS
@implements IDisposable
...
@code {
...
private IJSInProcessObjectReference? module;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var jsInProcess = (IJSInProcessRuntime)JS;
module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import",
"./scripts.js");
var value = module.Invoke<string>("javascriptFunctionIdentifier");
}
}
...
void IDisposable.Dispose()
{
if (module is not null)
{
await module.Dispose();
}
}
}
I det föregående exemplet fångas inte en JSDisconnectedException under modulhantering eftersom det inte finns någon Blazor–SignalR-krets i en Blazor WebAssembly-applikation som kan gå förlorad. Mer information finns i ASP.NET Core Blazor JavaScript-samverkan (JS interop).
Överväg att använda oformatterade anrop
Det här avsnittet gäller endast för Blazor WebAssembly appar.
När du kör på Blazor WebAssemblygår det att göra ohörda anrop från .NET till JavaScript. Det här är synkrona anrop som inte utför JSON-serialisering av argument eller returvärden. Alla aspekter av minneshantering och översättningar mellan .NET- och JavaScript-representationer är upp till utvecklaren.
Varning
Även om användningen av IJSUnmarshalledRuntime har minst omkostnader av JS interop-lösningar, är de JavaScript-API:er som krävs för att interagera med dessa API:er för närvarande odokumenterade och kan komma att ändras i framtida versioner.
function jsInteropCall() {
return BINDING.js_to_mono_obj("Hello world");
}
@inject IJSRuntime JS
@code {
protected override void OnInitialized()
{
var unmarshalledJs = (IJSUnmarshalledRuntime)JS;
var value = unmarshalledJs.InvokeUnmarshalled<string>("jsInteropCall");
}
}
Använd JavaScript [JSImport]
/[JSExport]
interop
JavaScript-[JSImport]
/[JSExport]
interop för Blazor WebAssembly-appar ger bättre prestanda och stabilitet över JS interop-API:et i ramverksversioner före ASP.NET Core i .NET 7.
Mer information finns i JavaScript JSImport/JSExport interop med ASP.NET Core Blazor.
AOT-kompilering
AOT-kompilering i förväg kompilerar en Blazor-appens .NET-kod direkt till den interna WebAssembly för direktkörning av webbläsaren. AOT-kompilerade appar resulterar i större appar som tar längre tid att ladda ned, men AOT-kompilerade appar ger vanligtvis bättre körningsprestanda, särskilt för appar som kör processorintensiva uppgifter. Mer information finns i ASP.NET Core Blazor WebAssembly byggverktyg och ahead-of-time (AOT) kompilering.
Minimera appens nedladdningsstorlek
Omlänkning vid körtid
Information om hur relinkning vid körning minimerar en appens nedladdningsstorlek finns i ASP.NET Core Blazor WebAssembly bygghjälpmedel och kompilering i förväg (AOT).
Använd System.Text.Json
Blazor JS interop-implementering förlitar sig på System.Text.Json, vilket är ett JSON-serialiseringsbibliotek med höga prestanda med låg minnesallokering. Att använda System.Text.Json bör inte resultera i ytterligare appnyttolaststorlek jämfört med att lägga till ett eller flera alternativa JSON-bibliotek.
Information om migrering finns i Så här migrerar du från Newtonsoft.Json
till System.Text.Json
.
Trimning av mellanliggande språk (IL)
Det här avsnittet gäller endast för scenarier på klientsidan Blazor.
Om du trimmar oanvända sammansättningar från en Blazor WebAssembly app minskar appens storlek genom att ta bort oanvänd kod i appens binärfiler. Mer information finns i Konfigurera Trimmer för ASP.NET Core Blazor.
Att länka en Blazor WebAssembly app minskar appens storlek genom att trimma oanvänd kod i appens binärfiler. IL-länkaren (Intermediate Language) aktiveras bara när du bygger i konfigurationen Release
. Om du vill dra nytta av detta publicerar du appen för distribution med hjälp av kommandot dotnet publish
med alternativet -c|--configuration inställt på Release
:
dotnet publish -c Release
Lazy load-sammansättningar
Det här avsnittet gäller endast för scenarier på klientsidan Blazor.
Läs in sammansättningar vid körning när sammansättningarna krävs av en väg. Mer information finns i Lazy Load-sammansättningar i ASP.NET Core Blazor WebAssembly.
Komprimering
Det här avsnittet gäller endast för Blazor WebAssembly appar.
När en Blazor WebAssembly app publiceras komprimeras utdata statiskt vid publicering för att minska appens storlek och ta bort överbelastning vid körningskomprimering. Blazor förlitar sig på servern för att utföra innehållsförhandling och hantera statiskt komprimerade filer.
När en app har distribuerats kontrollerar du att appen hanterar komprimerade filer. Granska fliken Network i en webbläsares utvecklarverktyg och kontrollera att filerna hanteras med Content-Encoding: br
(Brotli-komprimering) eller Content-Encoding: gz
(Gzip-komprimering). Om värden inte hanterar komprimerade filer följer du anvisningarna i Host och distribuerar ASP.NET Core Blazor WebAssembly.
Inaktivera oanvända funktioner
Det här avsnittet gäller endast för scenarier på klientsidan Blazor.
Blazor WebAssembly's runtime innehåller följande .NET-funktioner som kan inaktiveras för en mindre nyttolaststorlek:
-
Blazor WebAssembly har globaliseringsresurser som krävs för att visa värden, till exempel datum och valuta, i användarens kultur. Om appen inte kräver lokalisering kan du konfigurera appen så att den stöder den invarianta kulturen, som baseras på
en-US
kultur.
Att införa invariant globalisering resulterar bara i att icke-lokaliserade tidszonsnamn används. Om du vill trimma tidszonskod och data från appen använder du egenskapen
<InvariantTimezone>
MSBuild med värdettrue
i appens projektfil:<PropertyGroup> <InvariantTimezone>true</InvariantTimezone> </PropertyGroup>
Notera
<BlazorEnableTimeZoneSupport>
åsidosätter en tidigare<InvariantTimezone>
inställning. Vi rekommenderar att du tar bort inställningen<BlazorEnableTimeZoneSupport>
.
En datafil ingår för att göra tidszonsinformationen korrekt. Om appen inte kräver den här funktionen kan du inaktivera den genom att ange egenskapen
<BlazorEnableTimeZoneSupport>
MSBuild tillfalse
i appens projektfil:<PropertyGroup> <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport> </PropertyGroup>
Sorteringsinformation ingår för att få API:er som StringComparison.InvariantCultureIgnoreCase att fungera korrekt. Om du är säker på att appen inte kräver sorteringsdata kan du inaktivera den genom att ange egenskapen
BlazorWebAssemblyPreserveCollationData
MSBuild i appens projektfil tillfalse
:<PropertyGroup> <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData> </PropertyGroup>
ASP.NET Core