Osvědčené postupy pro výkon ASP.NET Core Blazor
Poznámka:
Toto není nejnovější verze tohoto článku. Aktuální verzi najdete v tomto článku ve verzi .NET 9.
Upozorňující
Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v zásadách podpory .NET a .NET Core. Aktuální verzi najdete v tomto článku ve verzi .NET 9.
Důležité
Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.
Aktuální verzi najdete v tomto článku ve verzi .NET 9.
Blazor je optimalizovaná pro vysoký výkon ve většině realistických scénářů uživatelského rozhraní aplikací. Nejlepší výkon ale závisí na vývojářích, kteří přijmou správné vzory a funkce.
Poznámka:
Příklady kódu v tomto článku přijímají referenční typy s možnou hodnotou null (NRT) a statickou analýzu stavu null-stav kompilátoru .NET, které jsou podporovány v ASP.NET Core v .NET 6 nebo novější.
Optimalizace rychlosti vykreslování
Optimalizujte rychlost vykreslování, abyste minimalizovali úlohy vykreslování a zlepšili rychlost odezvy uživatelského rozhraní, což může přinést desetinásobné nebo vyšší vylepšení rychlosti vykreslování uživatelského rozhraní.
Vyhněte se zbytečnému vykreslování podstromů součástí
Většinu nákladů na vykreslování nadřazené komponenty můžete odebrat tak, že při výskytu události přeskočíte překočení podřízených podstromů komponent. Měli byste se zabývat pouze vynecháváním podstromů rerenderingu, které jsou zvlášť nákladné k vykreslení a způsobují prodlevu uživatelského rozhraní.
Za běhu existují komponenty v hierarchii. Kořenová komponenta (první načtená komponenta) obsahuje podřízené komponenty. Naopak děti kořene mají své vlastní podřízené komponenty a tak dále. Když dojde k události, jako je například uživatel, který vybere tlačítko, určuje následující proces, které součásti se mají znovu vyřazuje:
- Událost je odeslána do komponenty, která vykreslovala obslužnou rutinu události. Po spuštění obslužné rutiny události se komponenta znovu vyenderuje.
- Když je komponenta znovu vysílaná, poskytuje nové kopii hodnot parametrů pro každou z jejích podřízených komponent.
- Po přijetí nové sady hodnot parametrů se každá komponenta rozhodne, zda se má znovu enderovat. Komponenty znovu vyřazuje, pokud se hodnoty parametrů mohly změnit, například pokud jsou proměnlivé objekty.
Poslední dva kroky předchozí sekvence pokračují rekurzivně dolů v hierarchii komponent. V mnoha případech je celý podstrom překreslován. Události, které cílí na komponenty vysoké úrovně, můžou způsobit nákladné opětovnéenderování, protože každá komponenta pod komponentou vysoké úrovně musí znovu enderovat.
Pokud chcete zabránit rekurzi vykreslování do určitého podstromu, použijte některý z následujících přístupů:
- Ujistěte se, že parametry podřízené komponenty jsou primitivní neměnné typy, jako
string
jsou ,int
,bool
,DateTime
a další podobné typy. Integrovaná logika pro detekci změn automaticky přeskočí rerendering, pokud se nezměnily primitivní neměnné hodnoty parametrů. Pokud vykreslujete podřízenou komponentu s typem<Customer CustomerId="item.CustomerId" />
CustomerId
int
, není komponenta znovu vygenerována,Customer
pokuditem.CustomerId
se nezmění. - Přepsání ShouldRender:
- Chcete-li přijmout neprimitivní hodnoty parametrů, jako jsou komplexní vlastní typy modelu, zpětné volání událostí nebo RenderFragment hodnoty.
- Pokud vytváření komponenty jen pro uživatelské rozhraní, která se po počátečním vykreslení nezmění, bez ohledu na změny hodnoty parametru.
Následující příklad vyhledávacího nástroje letecké společnosti používá soukromá pole ke sledování potřebných informací ke zjištění změn. Předchozí identifikátor příchozích letů (prevInboundFlightId
) a předchozí identifikátor odchozích letů (prevOutboundFlightId
) sledují informace pro další možnou aktualizaci komponent. Pokud se některý z identifikátorů letu změní, když jsou parametry komponenty nastaveny , OnParametersSet
komponenta je znovu vysunuta, protože shouldRender
je nastavena na true
. Pokud shouldRender
se vyhodnotí jako false
po kontrole identifikátorů letu, vyhnete se nákladnému rerenderu:
@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;
}
Obslužná rutina události může být také nastavena shouldRender
na true
. U většiny komponent obvykle není potřeba určit rerendering na úrovni jednotlivých obslužných rutin událostí.
Další informace naleznete v následujících zdrojích:
Virtualizace
Při vykreslování velkých objemů uživatelského rozhraní ve smyčce, například v seznamu nebo mřížce s tisíci položek, může při vykreslování uživatelského rozhraní dojít k prodlevě. Vzhledem k tomu, že uživatel vidí jenom malý počet prvků najednou bez posouvání, je často plýtvání časem vykreslování prvků, které nejsou aktuálně viditelné.
Blazor poskytuje komponentu Virtualize<TItem> pro vytvoření vzhledu a posouvání chování libovolného velkého seznamu při vykreslování pouze položek seznamu, které jsou v aktuálním zobrazení pro posouvání. Komponenta může například vykreslit seznam s 100 000 položkami, ale platí jenom náklady na vykreslování 20 položek, které jsou viditelné.
Vytváření jednoduchých a optimalizovaných komponent
Většina Razor komponent nevyžaduje agresivní optimalizaci, protože většina komponent se v uživatelském rozhraní neopakuje a neopakuje se s vysokou frekvencí. Například směrovatelné komponenty se direktivou @page
a komponentami, které se používají k vykreslení částí uživatelského rozhraní vysoké úrovně, jako jsou dialogy nebo formuláře, se s největší pravděpodobností zobrazují jenom jednou najednou a v reakci na gesto uživatele se zobrazí pouze jednou. Tyto komponenty obvykle nevytvoří vysokou úlohu vykreslování, takže můžete volně používat libovolnou kombinaci funkcí architektury, aniž byste se museli zabývat výkonem vykreslování.
Existují však běžné scénáře, kdy se komponenty opakují ve velkém měřítku a často vedou k nízkému výkonu uživatelského rozhraní:
- Velké vnořené formy se stovkami jednotlivých prvků, jako jsou vstupy nebo popisky.
- Mřížky se stovkami řádků nebo tisíci buněk
- Bodové grafy s miliony datových bodů
Pokud modeluje každý prvek, buňku nebo datový bod jako samostatnou instanci komponenty, často jich existuje tolik, aby jejich výkon vykreslování byl kritický. Tato část obsahuje rady k tomu, aby tyto komponenty byly jednoduché, aby uživatelské rozhraní zůstalo rychlé a responzivní.
Vyhněte se tisícům instancí komponent
Každá komponenta je samostatný ostrov, který se může vykreslit nezávisle na svých nadřazených a podřízených objektech. Výběrem způsobu rozdělení uživatelského rozhraní do hierarchie komponent přebíráte kontrolu nad členitostí vykreslování uživatelského rozhraní. Výsledkem může být dobrý nebo nízký výkon.
Rozdělením uživatelského rozhraní do samostatných komponent můžete mít menší části rerenderu uživatelského rozhraní, když dojde k událostem. V tabulce s mnoha řádky, které mají na každém řádku tlačítko, může být možné, že budete moct znovu vyřadit pouze jeden řádek pomocí podřízené komponenty místo celé stránky nebo tabulky. Každá komponenta ale vyžaduje další režii paměti a procesoru, aby se vyřešil její nezávislý stav a životní cyklus vykreslování.
V testu prováděném techniky jednotek produktu ASP.NET Core se v Blazor WebAssembly aplikaci zobrazila režie vykreslování přibližně 0,06 ms na instanci komponenty. Testovací aplikace vykreslovala jednoduchou komponentu, která přijímá tři parametry. Režie je z velké části způsobená načtením stavu jednotlivých součástí ze slovníků a předáváním a příjmem parametrů. Pomocí násobení můžete vidět, že přidání 2 000 dalších instancí součástí by do doby vykreslování přidalo 0,12 sekundy a uživatelské rozhraní by uživatelům začalo být pomalé.
Komponenty je možné usnadnit, abyste jich mohli mít více. Výkonnější technika je ale často, abyste se vyhnuli tolika komponentám, které se mají vykreslit. Následující části popisují dva přístupy, které můžete provést.
Další informace o správě paměti najdete v tématu Hostitel a nasazení aplikací na straně Blazorserveru ASP.NET Core.
Vložené podřízené součásti do jejich rodičů
Představte si následující část nadřazené komponenty, která vykresluje podřízené komponenty ve smyčce:
<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; }
}
Předchozí příklad funguje dobře, pokud se najednou nezobrazují tisíce zpráv. Pokud chcete zobrazit tisíce zpráv najednou, zvažte nefaktoring samostatné ChatMessageDisplay
komponenty. Místo toho vložte podřízenou komponentu do nadřazeného objektu. Následující přístup zabraňuje režijním nákladům na jednotlivé součásti vykreslování tak velkého počtu podřízených komponent za cenu ztráty schopnosti rerenderovat značky jednotlivých podřízených komponent nezávisle:
<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>
Definování opakovaně použitelného RenderFragments
v kódu
Podřízené komponenty můžete využít čistě jako způsob opětovného použití logiky vykreslování. V takovém případě můžete vytvořit opakovaně použitelnou logiku vykreslování bez implementace dalších komponent. V bloku libovolné komponenty @code
definujte .RenderFragment Vykreslí fragment z libovolného umístění tolikrát, kolikrát potřebujete:
@RenderWelcomeInfo
<p>Render the welcome content a second time:</p>
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!</p>;
}
Pokud chcete, aby RenderTreeBuilder byl kód opakovaně použitelný napříč více komponentami, deklarujte RenderFragmentpublic
static
public static RenderFragment SayHello = @<h1>Hello!</h1>;
SayHello
v předchozím příkladu lze vyvolat z nesouvisející komponenty. Tato technika je užitečná pro vytváření knihoven opakovaně použitelných fragmentů značek, které se vykreslují bez režie na jednotlivé komponenty.
RenderFragment delegáti mohou přijímat parametry. Následující komponenta předá delegátu message
zprávu (RenderFragment):
<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>;
}
Předchozí přístup opakovaně používá logiku vykreslování bez režie na jednotlivé součásti. Přístup ale neumožňuje nezávisle aktualizovat podstrom uživatelského rozhraní ani nemá možnost přeskočit vykreslování podstromu uživatelského rozhraní, když se jeho nadřazený objekt vykresluje, protože neexistuje žádná hranice komponent. Přiřazení delegáta RenderFragment je podporováno pouze v souborech komponent Razor (.razor
).
Pro nestatické pole, metodu nebo vlastnost, na kterou nelze odkazovat inicializátorem pole, například TitleTemplate
v následujícím příkladu, použijte vlastnost místo pole pro RenderFragment:
protected RenderFragment DisplayTitle =>
@<div>
@TitleTemplate
</div>;
Nepřibírat příliš mnoho parametrů
Pokud se komponenta opakuje velmi často, například stovky nebo tisícekrát, režie při předávání a přijímání jednotlivých parametrů se sestaví.
Je vzácné, že příliš mnoho parametrů výrazně omezuje výkon, ale může to být faktor.
TableCell
U komponenty, která v mřížce vykresluje 4 000krát, každý parametr předaný komponentě přidá do celkových nákladů na vykreslování přibližně 15 ms. Předání deseti parametrů vyžaduje přibližně 150 ms a způsobuje prodlevu vykreslování uživatelského rozhraní.
Pokud chcete snížit zatížení parametrů, sbalte několik parametrů do vlastní třídy. Například komponenta buňky tabulky může přijmout společný objekt. V následujícím příkladu Data
se pro každou buňku liší, ale Options
je společný pro všechny instance buněk:
@typeparam TItem
...
@code {
[Parameter]
public TItem? Data { get; set; }
[Parameter]
public GridOptions? Options { get; set; }
}
Mějte ale na paměti, že sdružování primitivních parametrů do třídy není vždy výhodou. I když může snížit počet parametrů, ovlivňuje také chování detekce a vykreslování změn. Předávání nepřimitivitních parametrů vždy aktivuje opětovné vykreslení, protože Blazor nemůže zjistit, jestli mají libovolné objekty interně proměnlivý stav, zatímco předávání primitivních parametrů aktivuje opětovné vykreslení pouze v případě, že se jejich hodnoty skutečně změnily.
Vezměte také v úvahu, že by mohlo být výhodnější nemít samostatnou součást buňky tabulky, jak ukazuje předchozí příklad, a místo toho začlenit její logiku přímo do nadřazené komponenty.
Poznámka:
Pokud je k dispozici více přístupů pro zlepšení výkonu, je obvykle potřeba provést srovnávací testy, aby bylo možné určit, který přístup poskytuje nejlepší výsledky.
Další informace o parametrech obecného typu (@typeparam
) najdete v následujících zdrojích informací:
- Referenční informace k syntaxi Razor pro ASP.NET Core
- komponenty ASP.NET Core Razor
- Komponenty ASP.NET Core Blazor bez vizuálního vzhledu
Ujistěte se, že jsou opravené kaskádové parametry.
Komponenta IsFixed
- Pokud
IsFixed
je (výchozí),false
každý příjemce kaskádové hodnoty nastaví odběr pro příjem oznámení o změnách. Každá z nich[CascadingParameter]
je podstatně dražší[Parameter]
sledování předplatného. - Pokud
IsFixed
jetrue
(například<CascadingValue Value="someValue" IsFixed="true">
), příjemci obdrží počáteční hodnotu, ale nenastaví předplatné pro příjem aktualizací. Každý[CascadingParameter]
je jednoduchý a není dražší než normální[Parameter]
.
Nastavení IsFixed
pro true
zvýšení výkonu, pokud existuje velký počet dalších komponent, které přijímají kaskádovou hodnotu. Pokud je to možné, nastavte IsFixed
na true
kaskádové hodnoty. Můžete nastavit IsFixed
true
, když se zadaná hodnota v průběhu času nezmění.
Pokud komponenta předává this
kaskádovou hodnotou, lze IsFixed
nastavit také na true
, protože this
se během životního cyklu komponenty nikdy nemění:
<CascadingValue Value="this" IsFixed="true">
<SomeOtherComponents>
</CascadingValue>
Další informace najdete v tématu Blazor Core.
Vyhněte se dělení atributů pomocí CaptureUnmatchedValues
Komponenty se můžou rozhodnout přijímat hodnoty parametrů bez neshod pomocí příznaku CaptureUnmatchedValues :
<div @attributes="OtherAttributes">...</div>
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? OtherAttributes { get; set; }
}
Tento přístup umožňuje předat elementu libovolné další atributy. Tento přístup je ale nákladný, protože renderer musí:
- Porovná všechny zadané parametry se sadou známých parametrů pro sestavení slovníku.
- Sledujte, jak se vzájemně přepisuje více kopií stejného atributu.
Použijte CaptureUnmatchedValues , kde výkon vykreslování součástí není kritický, například komponenty, které se často nepoužívají. U komponent, které se vykreslují ve velkém měřítku, například u každé položky ve velkém seznamu nebo v buňkách mřížky, se snažte vyhnout dělení atributů.
Další informace najdete v tématu ASP.NET splatting atributu Core Blazor a libovolné parametry.
Ruční implementace SetParametersAsync
Významným zdrojem režie vykreslování jednotlivých součástí je zápis příchozích hodnot parametrů do [Parameter]
vlastností. Renderer používá reflexi k zápisu hodnot parametrů, což může vést k nízkému výkonu ve velkém měřítku.
V některých extrémních případech se můžete chtít vyhnout reflexi a implementovat vlastní logiku nastavení parametrů ručně. To se může použít v těchto případech:
- Komponenta se vykreslí velmi často, například když v uživatelském rozhraní existují stovky nebo tisíce kopií komponenty.
- Komponenta přijímá mnoho parametrů.
- Zjistíte, že režie při příjmu parametrů má pozorovatelný dopad na odezvu uživatelského rozhraní.
Vextrémních SetParametersAsync Následující příklad záměrně zabraňuje vyhledávání slovníku:
@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);
}
}
Vrácení základní třídy SetParametersAsync v předchozím kódu spustí normální metodu životního cyklu bez opětovného přiřazení parametrů.
Jak vidíte v předchozím kódu, přepsání SetParametersAsync a dodání vlastní logiky je složité a pracné, takže obecně nedoporučujeme tento přístup používat. V extrémních případech může zvýšit výkon vykreslování o 20 až 25 %, ale tento přístup byste měli zvážit pouze v extrémních scénářích uvedených dříve v této části.
Neaktivujte události příliš rychle
Některé události prohlížeče se aktivuje velmi často. Může například onmousemove
onscroll
střílet desítky nebo stovkykrát za sekundu. Ve většině případů nemusíte provádět aktualizace uživatelského rozhraní často. Pokud se události aktivují příliš rychle, můžete poškodit odezvu uživatelského rozhraní nebo spotřebovat nadměrnou dobu procesoru.
Místo použití nativních událostí, které se rychle aktivují, zvažte použití zprostředkovatele komunikace k registraci zpětného JS volání, které se aktivuje méně často. Například následující komponenta zobrazí pozici myši, ale aktualizuje se maximálně jednou každých 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();
}
Odpovídající kód JavaScriptu zaregistruje naslouchací proces událostí MODELU DOM pro pohyb myši. V tomto příkladu naslouchací proces událostí používá throttle
Lodash k omezení frekvence volání:
<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>
Vyhněte se opakovanémuenderování po zpracování událostí bez změn stavu.
Komponenty dědí z ComponentBaseobjektu , který se automaticky vyvolá StateHasChanged po vyvolání obslužných rutin událostí komponenty. V některých případech může být zbytečné nebo nežádoucí aktivovat opětovné spuštění po vyvolání obslužné rutiny události. Obslužná rutina události například nemusí změnit stav komponenty. V těchto scénářích může aplikace využít IHandleEvent rozhraní k řízení chování Blazorzpracování událostí.
Poznámka:
Přístup v této části neprovádí výjimky z chybových hranic. Další informace a demonstrační kód, který podporuje hranice chyb voláním ComponentBase.DispatchExceptionAsync, naleznete v tématu AsNonRenderingEventHandler + ErrorBoundary = neočekávané chování (dotnet/aspnetcore
#54543).
Chcete-li zabránit rerenders pro všechny obslužné rutiny událostí komponenty, implementujte IHandleEvent a poskytněte IHandleEvent.HandleEventAsync úlohu, která vyvolá obslužnou rutinu události bez volání StateHasChanged.
V následujícím příkladu žádná obslužná rutina události přidaná do komponenty neaktivuje rerender, takže HandleSelect
při vyvolání nedojde k opětovnému rerenderu.
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);
}
Kromě toho, aby se zabránilo opětovnémuenderování po vyvolání obslužných rutin událostí v komponentě globálním způsobem, je možné zabránit opětovnémuenderu po jedné obslužné rutině události pomocí následující metody utility.
Přidejte do EventUtil
aplikace následující Blazor třídu. Statické akce a funkce v horní části EventUtil
třídy poskytují obslužné rutiny, které pokrývají několik kombinací argumentů a návratových typů, které Blazor se používají při zpracování událostí.
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);
}
}
Volání EventUtil.AsNonRenderingEventHandler
volání obslužné rutiny události, která při vyvolání neaktivuje vykreslení.
V následujícím příkladu:
- Výběr prvního tlačítka, které volá
HandleClick1
, aktivuje rerender. - Když vyberete druhé tlačítko, které volá
HandleClick2
, neaktivuje se rerender. - Výběr třetího tlačítka, které volá
HandleClick3
, neaktivuje rerender a používá argumenty události (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);
}
}
Kromě implementace IHandleEvent rozhraní může využití dalších osvědčených postupů popsaných v tomto článku také pomoct snížit nežádoucí vykreslení po zpracování událostí. Například přepsání ShouldRender v podřízených součástech cílové komponenty lze použít k řízení rerenderingu.
Vyhněte se opětovnému vytváření delegátů pro mnoho opakovaných prvků nebo součástí.
BlazorRekreace výrazů lambda delegátů pro prvky nebo komponenty ve smyčce může vést k nízkému výkonu.
Následující komponenta zobrazená v článku o zpracování událostí vykreslí sadu tlačítek. Každé tlačítko přiřadí delegáta ke své @onclick
události, což je v pořádku, pokud není k dispozici mnoho tlačítek k vykreslení.
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}";
}
}
Pokud se pomocí předchozího přístupu vykresluje velký počet tlačítek, má to nepříznivý dopad na rychlost vykreslování, což vede ke špatnému uživatelskému prostředí. Chcete-li vykreslit velký počet tlačítek s zpětným voláním pro události kliknutí, následující příklad používá kolekci objektů tlačítek, které přiřazují delegáta @onclick
každého tlačítka .Action Následující přístup nevyžaduje Blazor opětovné sestavení všech delegátů tlačítka při každém vykreslení tlačítek:
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 => { };
}
}
Optimalizace rychlosti spolupráce JavaScriptu
Volání mezi .NET a JavaScriptem vyžadují další režii, protože:
- Volání jsou asynchronní.
- Parametry a návratové hodnoty jsou serializované ve formátu JSON, aby poskytovaly snadno pochopitelný mechanismus převodu mezi typy .NET a JavaScript.
Kromě toho pro aplikace na straně Blazor serveru se tato volání předávají přes síť.
Vyhněte se příliš jemně odstupňovaným voláním
Vzhledem k tomu, že každé volání zahrnuje určité režijní náklady, může být užitečné snížit počet hovorů. Vezměte v úvahu následující kód, který ukládá kolekci položek v prohlížeči localStorage
:
private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
foreach (var item in items)
{
await JS.InvokeVoidAsync("localStorage.setItem", item.Id,
JsonSerializer.Serialize(item));
}
}
Předchozí příklad vytvoří samostatné JS volání vzájemné spolupráce pro každou položku. Místo toho následující přístup snižuje interoperabilitu JS na jedno volání:
private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
await JS.InvokeVoidAsync("storeAllInLocalStorage", items);
}
Odpovídající funkce JavaScriptu ukládá celou kolekci položek v klientovi:
function storeAllInLocalStorage(items) {
items.forEach(item => {
localStorage.setItem(item.id, JSON.stringify(item));
});
}
U Blazor WebAssembly aplikací se postupné volání jednotlivých JS zprostředkovatele komunikace do jednoho volání obvykle výrazně zvyšuje výkon pouze v případě, že komponenta provádí velký počet JS volání zprostředkovatele komunikace.
Zvažte použití synchronních volání.
Volání JavaScriptu z .NET
Tato část se týká pouze komponent na straně klienta.
JS interop volání jsou asynchronní, bez ohledu na to, zda je volaný kód synchronní nebo asynchronní. Volání jsou asynchronní, aby se zajistilo, že komponenty jsou kompatibilní napříč režimy vykreslování na straně serveru a na straně klienta. Na serveru musí být všechna JS volání zprostředkovatele komunikace asynchronní, protože se odesílají přes síťové připojení.
Pokud víte, že vaše komponenta běží jenom na WebAssembly, můžete se rozhodnout provádět synchronní JS volání interopu. To má o něco menší režii než provádění asynchronních volání a může vést k menšímu počtu cyklů vykreslování, protože při čekání na výsledky neexistuje žádný přechodný stav.
Pokud chcete v komponentě na straně klienta provést synchronní volání z .NET do JavaScriptu, přetypujte IJSRuntime hoIJSInProcessRuntime, aby JS bylo volání zprostředkovatele komunikace:
@inject IJSRuntime JS
...
@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
}
}
Při práci s komponentami IJSObjectReference na straně klienta ASP.NET Core 5.0 nebo novějším můžete místo toho použít IJSInProcessObjectReference synchronně. IJSInProcessObjectReference implementuje IAsyncDisposable/IDisposable a měla by být uvolněna pro uvolňování paměti, aby se zabránilo nevracení paměti, jak ukazuje následující příklad:
@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();
}
}
}
V předchozím příkladu JSDisconnectedException není při vyřazení modulu zachycený, protože v Blazor aplikaci není žádný SignalRokruh,Blazor WebAssembly který by se ztratil. Další informace najdete v tématu ASP.NET Interoperabilita Core Blazor JavaScriptu (JSinteroperabilita).
Volání .NET z JavaScriptu
Tato část se týká pouze komponent na straně klienta.
JS interop volání jsou asynchronní, bez ohledu na to, zda je volaný kód synchronní nebo asynchronní. Volání jsou asynchronní, aby se zajistilo, že komponenty jsou kompatibilní napříč režimy vykreslování na straně serveru a na straně klienta. Na serveru musí být všechna JS volání zprostředkovatele komunikace asynchronní, protože se odesílají přes síťové připojení.
Pokud víte, že vaše komponenta běží jenom na WebAssembly, můžete se rozhodnout provádět synchronní JS volání interopu. To má o něco menší režii než provádění asynchronních volání a může vést k menšímu počtu cyklů vykreslování, protože při čekání na výsledky neexistuje žádný přechodný stav.
Pokud chcete v komponentě na straně klienta provést synchronní volání z JavaScriptu do .NET, použijte DotNet.invokeMethod
místo DotNet.invokeMethodAsync
.
Synchronní volání fungují v následujících případech:
Tato část se týká pouze komponent na straně klienta.
JS interop volání jsou asynchronní, bez ohledu na to, zda je volaný kód synchronní nebo asynchronní. Volání jsou asynchronní, aby se zajistilo, že komponenty jsou kompatibilní napříč režimy vykreslování na straně serveru a na straně klienta. Na serveru musí být všechna JS volání zprostředkovatele komunikace asynchronní, protože se odesílají přes síťové připojení.
Pokud víte, že vaše komponenta běží jenom na WebAssembly, můžete se rozhodnout provádět synchronní JS volání interopu. To má o něco menší režii než provádění asynchronních volání a může vést k menšímu počtu cyklů vykreslování, protože při čekání na výsledky neexistuje žádný přechodný stav.
Pokud chcete v komponentě na straně klienta provést synchronní volání z .NET do JavaScriptu, přetypujte IJSRuntime hoIJSInProcessRuntime, aby JS bylo volání zprostředkovatele komunikace:
@inject IJSRuntime JS
...
@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
}
}
Při práci s komponentami IJSObjectReference na straně klienta ASP.NET Core 5.0 nebo novějším můžete místo toho použít IJSInProcessObjectReference synchronně. IJSInProcessObjectReference implementuje IAsyncDisposable/IDisposable a měla by být uvolněna pro uvolňování paměti, aby se zabránilo nevracení paměti, jak ukazuje následující příklad:
@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();
}
}
}
V předchozím příkladu JSDisconnectedException není při vyřazení modulu zachycený, protože v Blazor aplikaci není žádný SignalRokruh,Blazor WebAssembly který by se ztratil. Další informace najdete v tématu ASP.NET Interoperabilita Core Blazor JavaScriptu (JSinteroperabilita).
Zvažte použití nepotřebných volání.
Tato část platí jenom pro Blazor WebAssembly aplikace.
Při spuštění Blazor WebAssemblyje možné provádět neoznačené volání z .NET do JavaScriptu. Jedná se o synchronní volání, která neprovádějí serializaci argumentů json ani návratových hodnot. Všechny aspekty správy a překladů paměti mezi reprezentací .NET a JavaScriptem zůstanou na vývojáři.
Upozorňující
IJSUnmarshalledRuntime Používání má sice nejnižší režii přístupů JS k vzájemné spolupráci, ale rozhraní JAVAScript API potřebná k interakci s těmito rozhraními API jsou v současné době nezdokumentovaná a v budoucích verzích podléhají zásadním změnám.
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");
}
}
Použití zprostředkovatele komunikace JavaScriptu [JSImport]
/[JSExport]
Interoperabilita JavaScriptu [JSImport]
/[JSExport]
pro Blazor WebAssembly aplikace nabízí lepší výkon a stabilitu rozhraní API pro interoperabilitu JS v verzích rozhraní před ASP.NET Core v .NET 7.
Další informace naleznete v tématu JavaScript JSImport/JSExport interop s ASP.NET Core Blazor.
Kompilace AOT (Head-of-Time)
Kompilace AOT (Head-of-Time) zkompiluje Blazor kód .NET aplikace přímo do nativní webAssembly pro přímé spuštění prohlížečem. Kompilované aplikace AOT vedou ke stažení větších aplikací, ale kompilované aplikace AOT obvykle poskytují lepší výkon za běhu, zejména pro aplikace, které provádějí úlohy náročné na procesor. Další informace najdete v tématu ASP.NET nástroje sestavení Core Blazor WebAssembly a kompilace AOT (Head-of-Time).
Minimalizace velikosti stahování aplikací
Opětovné propojení modulu runtime
Informace o tom, jak opětovné propojení modulu runtime minimalizuje velikost stahování aplikace, najdete v tématu ASP.NET Nástroje sestavení Core Blazor WebAssembly a kompilace AOT (Head-of-Time).
Použití System.Text.Json
BlazorImplementace JS interoperability spoléhá na System.Text.Jsonto, že jde o vysoce výkonnou knihovnu serializace JSON s nedostatkem paměti. Použití System.Text.Json by nemělo mít za následek další velikost datové části aplikace při přidávání jedné nebo více alternativních knihoven JSON.
Pokyny k migraci najdete v tématu Jak migrovat z Newtonsoft.Json
do System.Text.Json
.
Oříznutí zprostředkujícího jazyka (IL)
Tato část se vztahuje pouze na scénáře na straně Blazor klienta.
Oříznutím nepoužívaných sestavení z Blazor WebAssembly aplikace se zmenšuje velikost aplikace odebráním nepoužívaného kódu v binárních souborech aplikace. Další informace naleznete v tématu Konfigurace trimmeru pro ASP.NET Core Blazor.
Blazor WebAssembly Propojení aplikace zmenšuje velikost aplikace oříznutím nepoužívaného kódu v binárních souborech aplikace. Linker zprostředkujícího jazyka (IL) je povolen pouze při sestavování v Release
konfiguraci. Pokud chcete tuto výhodu využít, publikujte aplikaci pro nasazení pomocí dotnet publish
příkazu s parametrem -c|--configuration nastaveným na Release
:
dotnet publish -c Release
Opožděná načtení sestavení
Tato část se vztahuje pouze na scénáře na straně Blazor klienta.
Načtěte sestavení za běhu, pokud jsou sestavení požadovaná trasou. Další informace naleznete v tématu Opožděné načtení sestavení v ASP.NET Core Blazor WebAssembly.
Komprese
Tato část platí jenom pro Blazor WebAssembly aplikace.
Blazor WebAssembly Když je aplikace publikovaná, výstup se během publikování staticky komprimuje, aby se snížila velikost aplikace a odstranila režijní náklady na kompresi za běhu. Blazor spoléhá na server k provádění vyjednávání obsahu a obsluhuje staticky komprimované soubory.
Po nasazení aplikace ověřte, že aplikace obsluhuje komprimované soubory.
Zkontrolujte kartu Síť v vývojářských nástrojích prohlížeče a ověřte, že se soubory obsluhují s Content-Encoding: br
(komprese Brotli) nebo Content-Encoding: gz
(komprese Gzip). Pokud hostitel obsluhuje komprimované soubory, postupujte podle pokynů v části Hostitel a nasaďte ASP.NET Core Blazor WebAssembly.
Zakázání nepoužívaných funkcí
Tato část se vztahuje pouze na scénáře na straně Blazor klienta.
Blazor WebAssemblyModul runtime obsahuje následující funkce .NET, které je možné zakázat pro menší velikost datové části:
-
Blazor WebAssembly provádí globalizační prostředky potřebné k zobrazení hodnot, jako jsou kalendářní data a měna, v jazykové verzi uživatele. Pokud aplikace nevyžaduje lokalizaci, můžete ji nakonfigurovat tak, aby podporovala neutrální jazykovou verzi, která je založená
en-US
na jazykové verzi.
Přijetí invariantní globalizace vede pouze k použití nelokalizačních názvů časových pásem. Pokud chcete oříznout kód časového pásma a data z aplikace, použijte
<InvariantTimezone>
vlastnost MSBuild s hodnotoutrue
v souboru projektu aplikace:<PropertyGroup> <InvariantTimezone>true</InvariantTimezone> </PropertyGroup>
Poznámka:
<BlazorEnableTimeZoneSupport>
přepíše dřívější<InvariantTimezone>
nastavení. Doporučujeme odebrat<BlazorEnableTimeZoneSupport>
nastavení.
Součástí datového souboru je oprava informací o časovém pásmu. Pokud aplikace tuto funkci nevyžaduje, zvažte její zakázání nastavením
<BlazorEnableTimeZoneSupport>
vlastnosti MSBuild dofalse
souboru projektu aplikace:<PropertyGroup> <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport> </PropertyGroup>
Informace o kolaci jsou zahrnuty, aby rozhraní API fungovala StringComparison.InvariantCultureIgnoreCase správně. Pokud jste si jistí, že aplikace nevyžaduje kolační data, zvažte její zakázání nastavením
BlazorWebAssemblyPreserveCollationData
vlastnosti MSBuild v souboru projektu aplikace nafalse
:<PropertyGroup> <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData> </PropertyGroup>