Sdílet prostřednictvím


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 ve verzi .NET 8 tohoto článku.

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 ve verzi .NET 8 tohoto článku.

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:

  1. 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.
  2. Když je komponenta znovu vysílaná, poskytuje nové kopii hodnot parametrů pro každou z jejích podřízených komponent.
  3. Po přijetí nové sady hodnot parametrů se každá komponenta rozhodne, zda se má znovu enderovat. Ve výchozím nastavení komponenty znovu vyřazuje, pokud se hodnoty parametrů mohly změnit, například pokud jde o 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 stringjsou , int, bool, DateTimea 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" />CustomerIdint, není komponenta znovu vygenerována, Customer pokud item.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 , OnParametersSetkomponenta 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é.

Další informace najdete v tématu ASP.NET virtualizace komponent CoreRazor.

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 RenderFragmentpublicstatic

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 RenderFragment zprávu (message):

<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 se podporuje jenom v Razor souborech komponent (.razor) a zpětné volání událostí se nepodporuje.

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

Vezměte však v úvahu, že by mohlo být vylepšením, že neobsahuje součást buňky tabulky, jak je znázorněno v předchozím příkladu, a místo toho vložte její logiku 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í:

Ujistěte se, že jsou opravené kaskádové parametry.

Komponenta CascadingValue má volitelný IsFixed parametr:

  • 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žší než běžná [Parameter] kvůli sledování předplatného.
  • Pokud IsFixed je true (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 IsFixedtrue , když se zadaná hodnota v průběhu času nezmění.

Pokud komponenta předává this kaskádovou hodnotu, IsFixed lze také nastavit na true:

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

Další informace najdete v tématu ASP.NET Kaskádové hodnoty a parametry CoreBlazor.

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 onmousemoveonscroll 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á funkci Lodash throttle 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.

Ve výchozím nastavení komponenty dědí z ComponentBase, 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í.

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 Blazor aplikace následující EventUtil 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:

  • Ve výchozím nastavení jsou volání asynchronní.
  • Ve výchozím nastavení se JSparametry a návratové hodnoty serializují, 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.

Volání zprostředkovatele komunikace JS jsou ve výchozím nastavení asynchronní, bez ohledu na to, jestli volaný kód je synchronní nebo asynchronní. Volání jsou ve výchozím nastavení 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 hoJS, aby IJSInProcessRuntime 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 IAsyncDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", 
            "./scripts.js");
        }
    }

    ...

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
}

Volání .NET z JavaScriptu

Tato část se týká pouze komponent na straně klienta.

Volání zprostředkovatele komunikace JS jsou ve výchozím nastavení asynchronní, bez ohledu na to, jestli volaný kód je synchronní nebo asynchronní. Volání jsou ve výchozím nastavení 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:

  • Komponenta se vykreslí pouze pro provádění ve službě WebAssembly.
  • Volaná funkce vrátí synchronně hodnotu. Funkce není async metoda a nevrací .NET Task ani JavaScript Promise.

Tato část se týká pouze komponent na straně klienta.

Volání zprostředkovatele komunikace JS jsou ve výchozím nastavení asynchronní, bez ohledu na to, jestli volaný kód je synchronní nebo asynchronní. Volání jsou ve výchozím nastavení 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 hoJS, aby IJSInProcessRuntime 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 IAsyncDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", 
            "./scripts.js");
        }
    }

    ...

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
}

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í JSserializaci argumentů nebo 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 interoperability 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

BlazorJS Implementace vzájemné spolupráce spoléhá na System.Text.Jsonto, což je vysoce výkonná JSknihovna serializace ON s nízkým přidělením paměti. Použití System.Text.Json by nemělo mít za následek další velikost datové části aplikace při přidání jedné nebo více alternativních JSknihoven ON.

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 platí jenom pro Blazor WebAssembly aplikace.

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. Ve výchozím nastavení je linker zprostředkujícího jazyka (IL) 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 platí jenom pro Blazor WebAssembly aplikace.

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 platí jenom pro Blazor WebAssembly aplikace.

Blazor WebAssemblyModul runtime obsahuje následující funkce .NET, které je možné zakázat pro menší velikost datové části:

  • 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 v souboru projektu aplikace na false:

    <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 na false:

    <PropertyGroup>
      <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
    </PropertyGroup>
    
  • Ve výchozím nastavení Blazor WebAssembly má prostředky globalizace 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.