ASP.NET Core Blazor WebAssembly najlepszych rozwiązań dotyczących wydajnościASP.NET Core Blazor WebAssembly performance best practices

Blazor WebAssembly jest starannie zaprojektowany i zoptymalizowany pod kątem wysokiej wydajności w najbardziej realistycznych scenariuszach interfejsu użytkownika aplikacji.Blazor WebAssembly is carefully designed and optimized to enable high performance in most realistic application UI scenarios. Jednak generowanie najlepszych wyników zależy od deweloperów korzystających z odpowiednich wzorców i funkcji.However, producing the best results depends on developers using the right patterns and features. Należy wziąć pod uwagę następujące aspekty:Consider the following aspects:

  • Przepływność środowiska uruchomieniowego: kod platformy .NET działa w interpreterze środowiska uruchomieniowego webassembly, więc PRZEPŁYWNOŚĆ procesora CPU jest ograniczona.Runtime throughput: The .NET code runs on an interpreter within the WebAssembly runtime, so CPU throughput is limited. W wymagających scenariuszach aplikacja korzysta z optymalizacji szybkości renderowania.In demanding scenarios, the app benefits from optimizing rendering speed.
  • Czas uruchamiania: aplikacja przenosi środowisko uruchomieniowe platformy .NET do przeglądarki, dlatego ważne jest, aby użyć funkcji minimalizujących rozmiar pobieranych aplikacji.Startup time: The app transfers a .NET runtime to the browser, so it's important to use features that minimize the application download size.

Optymalizuj szybkość renderowaniaOptimize rendering speed

Poniższe sekcje zawierają zalecenia dotyczące minimalizowania obciążeń renderowania i zwiększania czasu odpowiedzi interfejsu użytkownika.The following sections provide recommendations to minimize rendering workload and improve UI responsiveness. Poniższe porady mogą łatwo wprowadzić dziesięć lub większą poprawę w zakresie szybkości renderowania interfejsu użytkownika.Following this advice could easily make a ten-fold or higher improvement in UI rendering speeds.

Unikaj niepotrzebnego renderowania poddrzewa składnikówAvoid unnecessary rendering of component subtrees

W środowisku uruchomieniowym składniki istnieją jako hierarchia.At runtime, components exist as a hierarchy. Składnik główny ma składniki podrzędne.A root component has child components. Z kolei dzieci mają własne składniki podrzędne i tak dalej.In turn, the root's children have their own child components, and so on. W przypadku wystąpienia zdarzenia, takiego jak użytkownik, który wybiera przycisk, w ten sposób Blazor decyduje o tym, które składniki mają być przerenderowane:When an event occurs, such as a user selecting a button, this is how Blazor decides which components to rerender:

  1. Samo zdarzenie jest wysyłane do składnika, który wyrenderuje procedurę obsługi zdarzenia.The event itself is dispatched to whichever component rendered the event's handler. Po wykonaniu procedury obsługi zdarzeń ten składnik jest renderowany.After executing the event handler, that component is rerendered.
  2. Zawsze, gdy dowolny składnik jest przerenderowany, dostarcza nową kopię wartości parametrów do poszczególnych składników podrzędnych.Whenever any component is rerendered, it supplies a new copy of the parameter values to each of its child components.
  3. W przypadku otrzymania nowego zestawu wartości parametrów każdy składnik wybiera, czy ma być przerenderowany.When receiving a new set of parameter values, each component chooses whether to rerender. Domyślnie składniki są rerenderowane, jeśli wartości parametrów mogły ulec zmianie (na przykład, jeśli są to obiekty modyfikowalne).By default, components rerender if the parameter values may have changed (for example, if they are mutable objects).

Ostatnie dwa kroki tej sekwencji kontynuują rekursywnie hierarchię składników.The last two steps of this sequence continue recursively down the component hierarchy. W wielu przypadkach całe poddrzewo jest renderowane.In many cases, the entire subtree is rerendered. Oznacza to, że zdarzenia ukierunkowane na składniki wyższego poziomu mogą spowodować kosztowne procesy ponownego renderowania, ponieważ wszystko poniżej tego punktu musi być renderowane.This means that events targeting high-level components can cause expensive rerendering processes because everything below that point must be rerendered.

Jeśli chcesz przerwać ten proces i zapobiec obsłużeniu rekursji do określonego poddrzewa, możesz:If you want to interrupt this process and prevent rendering recursion into a particular subtree, then you can either:

  • Upewnij się, że wszystkie parametry określonego składnika są typami pierwotnymi (na przykład,,,, string int bool DateTime i innych).Ensure that all parameters to a certain component are of primitive immutable types (for example, string, int, bool, DateTime, and others). Wbudowana logika do wykrywania zmian automatycznie pomija ponowne renderowanie, jeśli żadna z tych wartości parametrów nie została zmieniona.The built-in logic for detecting changes automatically skips rerendering if none of these parameter values have changed. Jeśli renderuje składnik podrzędny z <Customer CustomerId="@item.CustomerId" /> , gdzie CustomerId jest int wartością, wówczas nie jest on ponownie renderowany z wyjątkiem item.CustomerId zmian.If you render a child component with <Customer CustomerId="@item.CustomerId" />, where CustomerId is an int value, then it isn't rerendered except when item.CustomerId changes.
  • Jeśli musisz zaakceptować wartości parametrów niepierwotnych, takie jak niestandardowe typy modeli, wywołania zwrotne zdarzeń lub RenderFragment wartości, możesz przesłonić, ShouldRender Aby kontrolować decyzję o tym, czy należy renderować, co zostało opisane w sekcji ShouldRender użycie .If you need to accept nonprimitive parameter values, such as custom model types, event callbacks, or RenderFragment values, then you can override ShouldRender to control the decision about whether to render, which is described in the Use of ShouldRender section.

Po pominięciu odrenderowania całych poddrzew może być możliwe usunięcie ogromnej większości kosztów renderowania w przypadku wystąpienia zdarzenia.By skipping rerendering of whole subtrees, you may be able to remove the vast majority of the rendering cost when an event occurs.

W celu pominięcia odwzorowania tej części interfejsu użytkownika warto uwzględnić składniki podrzędne.You may wish to factor out child components specifically so that you can skip rerendering that part of the UI. Jest to prawidłowy sposób zmniejszenia kosztów renderowania składnika nadrzędnego.This is a valid way to reduce the rendering cost of a parent component.

Korzystanie z ShouldRenderUse of ShouldRender

W przypadku tworzenia składnika tylko interfejsu użytkownika, który nigdy nie zmienia się po początkowym renderowaniu (bez względu na wartości parametrów), skonfiguruj, ShouldRender Aby zwrócić false :If authoring a UI-only component that never changes after the initial render (regardless of any parameter values), configure ShouldRender to return false:

@code {
    protected override bool ShouldRender() => false;
}

Jeśli składnik wymaga ponownej renderowania, gdy jego wartości parametrów są zmieniane w określony sposób, można użyć pól prywatnych do śledzenia niezbędnych informacji w celu wykrycia zmian.If the component only requires rerendering when its parameter values mutate in particular ways, then you can use private fields to track the necessary information to detect changes. W poniższym przykładzie shouldRender jest oparta na sprawdzaniu dowolnego rodzaju zmiany lub mutacji, które powinny monitować o przerenderowanie.In the following example, shouldRender is based on checking for any kind of change or mutation that should prompt a rerender. prevOutboundFlightId i prevInboundFlightId Śledź informacje dotyczące następnej potencjalnej aktualizacji:prevOutboundFlightId and prevInboundFlightId track information for the next potential update:

@code {
    [Parameter]
    public FlightInfo OutboundFlight { get; set; }
    
    [Parameter]
    public FlightInfo InboundFlight { get; set; }

    private int prevOutboundFlightId;
    private int prevInboundFlightId;
    private bool shouldRender;

    protected override void OnParametersSet()
    {
        shouldRender = OutboundFlight.FlightId != prevOutboundFlightId
            || InboundFlight.FlightId != prevInboundFlightId;

        prevOutboundFlightId = OutboundFlight.FlightId;
        prevInboundFlightId = InboundFlight.FlightId;
    }

    protected override bool ShouldRender() => shouldRender;

    // Note that 
}

W powyższym kodzie, program obsługi zdarzeń może być również ustawiony shouldRender na true tak, aby składnik został przerenderowany po zdarzeniu.In the preceding code, an event handler may also set shouldRender to true so that the component is rerendered after the event.

W przypadku większości składników ten poziom kontroli ręcznej nie jest konieczny.For most components, this level of manual control isn't necessary. Należy tylko zależeć od pomijania poddrzew renderowania, jeśli te poddrzewa są szczególnie kosztowne do renderowania i powodują opóźnienia interfejsu użytkownika.You should only be concerned about skipping rendering subtrees if those subtrees are particularly expensive to render and are causing UI lag.

Aby uzyskać więcej informacji, zobacz RazorCykl życia składnika ASP.NET Core.For more information, see RazorCykl życia składnika ASP.NET Core.

WirtualizacjaVirtualization

Podczas renderowania dużych ilości interfejsu użytkownika w obrębie pętli, na przykład lista lub siatka z tysiącami wpisów, zawiera ilość operacji renderowania może prowadzić do opóźnienia w renderowaniu interfejsu użytkownika i w ten sposób słabe środowisko użytkownika.When rendering large amounts of UI within a loop, for example a list or grid with thousands of entries, the sheer quantity of rendering operations can lead to a lag in UI rendering and thus a poor user experience. W przypadku, gdy użytkownik widzi tylko niewielką liczbę elementów jednocześnie bez przewijania, wydaje się, że wastefule to wiele czasu renderowania elementów, które nie są obecnie widoczne.Given that the user can only see a small number of elements at once without scrolling, it seems wasteful to spend so much time rendering elements that aren't currently visible.

Aby rozwiązać ten wpływ, program Blazor udostępnia Virtualize składnik, który tworzy wygląd i zachowanie przewijania dla arbitralnie dużej listy, ale tylko renderuje elementy listy, które znajdują się w bieżącym okienku ekranu przewijania.To address this, Blazor provides the Virtualize component that creates the appearance and scroll behaviors of an arbitrarily-large list but only renders the list items that are within the current scroll viewport. Na przykład oznacza to, że aplikacja może mieć listę z 100 000 wpisów, ale płacisz kosztem renderowania 20 elementów, które są widoczne w dowolnym momencie.For example, this means that the app can have a list with 100,000 entries but only pay the rendering cost of 20 items that are visible at any one time. Użycie Virtualize składnika może skalować wydajność interfejsu użytkownika według kolejności wielkości.Use of the Virtualize component can scale up UI performance by orders of magnitude.

Aby uzyskać więcej informacji, zobacz BlazorWirtualizacja składników ASP.NET Core.For more information, see BlazorWirtualizacja składników ASP.NET Core.

Twórz lekkie, zoptymalizowane składnikiCreate lightweight, optimized components

Większość Blazor składników nie wymaga agresywnej optymalizacji.Most Blazor components don't require aggressive optimization efforts. Wynika to z faktu, że większość składników nie jest często powtarzana w interfejsie użytkownika i nie jest uruchamiana z dużą częstotliwością.This is because most components don't often repeat in the UI and don't rerender at high frequency. Na przykład @page składniki i składniki reprezentujące jednopoziomowe elementy interfejsu użytkownika, takie jak okna dialogowe lub formularze, najprawdopodobniej pojawiają się tylko jeden w czasie i ponownie renderują w odpowiedzi na gest użytkownika.For example, @page components and components representing high-level UI pieces such as dialogs or forms, most likely appear only one at a time and only rerender in response to a user gesture. Te składniki nie tworzą obciążenia o wysokim poziomie renderowania, dzięki czemu można swobodnie korzystać z dowolnej kombinacji potrzebnych funkcji, bez obaw o wydajność renderowania.These components don't create a high rendering workload, so you can freely use any combination of framework features you want without worrying much about rendering performance.

Istnieją jednak również typowe scenariusze, w których można tworzyć składniki, które muszą być powtórzone na dużą skalę.However, there are also common scenarios where you build components that need to be repeated at scale. Na przykład:For example:

  • Duże zagnieżdżone formularze mogą mieć setki poszczególnych wejść, etykiet i innych elementów.Large nested forms may have hundreds of individual inputs, labels, and other elements.
  • Siatki mogą zawierać tysiące komórek.Grids may have thousands of cells.
  • Wykresy punktowe mogą mieć miliony punktów danych.Scatter plots may have millions of data points.

Jeśli modeluje każdą jednostkę jako oddzielne wystąpienia składnika, będzie wiele z nich, aby wydajność renderowania stała się krytyczna.If modelling each unit as separate component instances, there will be so many of them that their rendering performance does become critical. Ta sekcja zawiera wskazówki dotyczące podejmowania takich składników w sposób uproszczony, co sprawia, że interfejs użytkownika pozostanie szybko i będzie odpowiadać.This section provides advice on making such components lightweight so that the UI remains fast and responsive.

Unikaj tysięcy wystąpień składnikówAvoid thousands of component instances

Każdy składnik jest oddzielną wyspa, która może być niezależna od elementów nadrzędnych i podrzędnych.Each component is a separate island that can render independently of its parents and children. Wybierając sposób dzielenia interfejsu użytkownika na hierarchię składników, można przejąć kontrolę nad szczegółowością renderowania interfejsu użytkownika.By choosing how to split up the UI into a hierarchy of components, you are taking control over the granularity of UI rendering. Może to być dobre lub złe w przypadku wydajności.This can be either good or bad for performance.

  • Dzieląc interfejs użytkownika na więcej składników, można przetworzyć mniejsze części interfejsu użytkownika, gdy wystąpią zdarzenia.By splitting the UI into more components, you can have smaller portions of the UI rerender when events occur. Na przykład gdy użytkownik kliknie przycisk w wierszu tabeli, może być możliwe tylko przerenderowanie pojedynczego wiersza zamiast całej strony lub tabeli.For example when a user clicks a button in a table row, you may be able to have only that single row rerender instead of the whole page or table.
  • Jednak każdy dodatkowy składnik wiąże się z dodatkowym obciążeniem pamięci i procesora, aby zająć się niezależnym stanem i wyrenderowaniem.However, each extra component involves some extra memory and CPU overhead to deal with its independent state and rendering lifecycle.

Podczas dostrajania wydajności programu Blazor WebAssembly .NET 5 mierzy się obciążenie renderowania wokół 0,06 MS na wystąpienie składnika.When tuning the performance of Blazor WebAssembly on .NET 5, we measured a rendering overhead of around 0.06 ms per component instance. Jest to oparte na prostym składniku, który akceptuje trzy parametry działające na typowym laptopie.This is based on a simple component that accepts three parameters running on a typical laptop. Wewnętrznie narzuty są duże ze względu na pobieranie stanu poszczególnych składników ze słowników oraz przekazywanie i otrzymywanie parametrów.Internally, the overhead is largely due to retrieving per-component state from dictionaries and passing and receiving parameters. Dzięki mnożenia można zobaczyć, że dodanie 2 000 dodatkowych wystąpień składników spowodowałoby dodanie 0,12 sekund do czasu renderowania, a interfejs użytkownika byłby wolny dla użytkowników.By multiplication, you can see that adding 2,000 extra component instances would add 0.12 seconds to the rendering time and the UI would begin feeling slow to users.

Istnieje możliwość, że składniki są bardziej uproszczone, dzięki czemu można uzyskać więcej z nich, ale często bardziej wydajną techniką nie jest to wiele składników.It's possible to make components more lightweight so that you can have more of them, but often the more powerful technique is not to have so many components. W poniższych sekcjach opisano dwa sposoby.The following sections describe two approaches.

Wbudowane składniki podrzędne do ich elementów nadrzędnychInline child components into their parents

Rozważmy następujący składnik, który renderuje sekwencję składników podrzędnych:Consider the following component that renders a sequence of child components:

<div class="chat">
    @foreach (var message in messages)
    {
        <ChatMessageDisplay Message="@message" />
    }
</div>

Dla poprzedniego przykładowego kodu <ChatMessageDisplay> składnik jest zdefiniowany w pliku ChatMessageDisplay.razor zawierającym:For the preceding example code, the <ChatMessageDisplay> component is defined in a file ChatMessageDisplay.razor containing:

<div class="chat-message">
    <span class="author">@Message.Author</span>
    <span class="text">@Message.Text</span>
</div>

@code {
    [Parameter]
    public ChatMessage Message { get; set; }
}

Powyższy przykład działa prawidłowo i sprawdza, czy tylko tysiące wiadomości nie są wyświetlane jednocześnie.The preceding example works fine and performs well as long as thousands of messages aren't shown at once. Aby jednocześnie pokazać tysiące komunikatów, należy rozważyć rozważenia oddzielnego ChatMessageDisplay składnika.To show thousands of messages at once, consider not factoring out the separate ChatMessageDisplay component. Zamiast tego Renderuj wbudowane bezpośrednio do obiektu nadrzędnego:Instead, inline the rendering directly into the parent:

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

Pozwala to uniknąć narzutu na składnik renderowania, tak aby wiele składników podrzędnych była kosztem niemożliwości ponownego renderowania każdego z nich.This avoids the per-component overhead of rendering so many child components at the cost of not being able to rerender each of them independently.

Zdefiniuj wielokrotne użycie RenderFragments koduDefine reusable RenderFragments in code

Składniki podrzędne mogą być rozprowadzone wyłącznie jako sposób użycia logiki renderowania.You may be factoring out child components purely as a way of reusing rendering logic. W takim przypadku nadal można ponownie używać logiki renderowania bez deklarowania rzeczywistych składników.If that's the case, it's still possible to reuse rendering logic without declaring actual components. W bloku dowolnego składnika @code można zdefiniować RenderFragment , który EMITUJE interfejs użytkownika i może być wywoływany z dowolnego miejsca:In any component's @code block, you can define a RenderFragment that emits UI and can be called from anywhere:

<h1>Hello, world!</h1>

@RenderWelcomeInfo

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

            <SurveyPrompt Title="How is Blazor working for you?" />
        </div>
    };
}

Jak demonstated w poprzednim przykładzie, składniki mogą emitować znaczniki z kodu w obrębie ich @code bloku i poza nim.As demonstated in the preceding example, components can emit markup from code within their @code block and outside it. To podejście definiuje RenderFragment delegata, który można renderować wewnątrz zwykłych wyników renderowania składnika, opcjonalnie w wielu miejscach.This approach defines a RenderFragment delegate that you can render inside the component's normal render output, optionally in multiple places. Jest to konieczne, aby delegat zaakceptował parametr o nazwie __builder typu, RenderTreeBuilder Aby Razor kompilator mógł generować instrukcje renderowania dla niego.It's necessary for the delegate to accept a parameter called __builder of type RenderTreeBuilder so that the Razor compiler can produce rendering instructions for it.

Jeśli chcesz umożliwić wielokrotne użycie wielu składników, rozważ zadeklarowanie go jako public static członka:If you want to make this reusable across multiple components, consider declaring it as a public static member:

public static RenderFragment SayHello = __builder =>
{
    <h1>Hello!</h1>
};

Ta funkcja może teraz zostać wywołana z niepowiązanego składnika.This could now be invoked from an unrelated component. Ta technika jest przydatna do tworzenia bibliotek wstawek znaczników wielokrotnego użytku, które są renderowane bez jakichkolwiek obciążeń dla składników.This technique is useful for building libraries of reusable markup snippets that render without any per-component overhead.

RenderFragment Delegaty mogą również akceptować parametry.RenderFragment delegates can also accept parameters. Aby utworzyć odpowiednik ChatMessageDisplay składnika z wcześniejszego przykładu:To create the equivalent of the ChatMessageDisplay component from the earlier example:

<div class="chat">
    @foreach (var message in messages)
    {
        @ChatMessageDisplay(message)
    }
</div>

@code {
    private RenderFragment<ChatMessage> ChatMessageDisplay = message => __builder =>
    {
        <div class="chat-message">
            <span class="author">@message.Author</span>
            <span class="text">@message.Text</span>
        </div>
    };
}

Takie podejście umożliwia ponowne korzystanie z logiki renderowania bez narzutu na składnik.This approach provides the benefit of reusing rendering logic without per-component overhead. Nie ma jednak możliwości odświeżenia poddrzewa interfejsu użytkownika niezależnie od tego, czy nie ma możliwości pominięcia renderowania tego poddrzewa interfejsu użytkownika podczas jego nadrzędnego renderowania, ponieważ nie ma granicy składnika.However, it doesn't have the benefit of being able to refresh its subtree of the UI independently, nor does it have the ability to skip rendering that subtree of the UI when its parent renders, since there's no component boundary.

Dla niestatycznego pola, metody lub właściwości, do których nie można odwoływać się inicjatora pola, na przykład TitleTemplate w poniższym przykładzie, użyj właściwości zamiast pola dla RenderFragment :For a non-static field, method, or property that can't be referenced by a field initializer, such as TitleTemplate in the following example, use a property instead of a field for the RenderFragment:

protected RenderFragment DisplayTitle => __builder =>
{
    <div>
        @TitleTemplate
    </div>   
};

Nie odbieraj zbyt wielu parametrówDon't receive too many parameters

Jeśli składnik powtarza się bardzo często, na przykład setki lub tysiące razy, należy pamiętać, że narzuty dotyczące przekazywania i otrzymywania każdego parametru kompiluje.If a component repeats extremely often, for example hundreds or thousands of times, then bear in mind that the overhead of passing and receiving each parameter builds up.

Jest to rzadkie, że zbyt wiele parametrów poważnie ogranicza wydajność, ale może być czynnikiem.It's rare that too many parameters severely restricts performance, but it can be a factor. Dla <TableCell> składnika, który renderuje 1 000 razy w obrębie siatki, każdy dodatkowy parametr, który przeszedł do niego, może dodać około 15 MS do całkowitego kosztu renderowania.For a <TableCell> component that renders 1,000 times within a grid, each extra parameter passed to it could add around 15 ms to the total rendering cost. Jeśli każda komórka zaakceptowała 10 parametrów, przekazywanie parametrów zajmie około 150 MS na składnik renderowania i w ten sposób prawdopodobnie 150 000 MS (150 s) i z własnej przyczyny jest interfejs użytkownika.If each cell accepted 10 parameters, parameter passing takes around 150 ms per component render and thus perhaps 150,000 ms (150 seconds) and on its own cause a laggy UI.

Aby zmniejszyć obciążenie, można powiązać wiele parametrów za pośrednictwem klas niestandardowych.To reduce this load, you could bundle together multiple parameters via custom classes. Na przykład <TableCell> składnik może zaakceptować:For example, a <TableCell> component might accept:

@typeparam TItem

...

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

W poprzednim przykładzie Data różni się dla każdej komórki, ale Options jest wspólne dla wszystkich.In the preceding example, Data is different for every cell, but Options is common across all of them. Oczywiście może być udoskonaleniem, aby nie mieć <TableCell> składnika i zamiast niego wbudowany w jego logikę w składnik nadrzędny.Of course, it might be an improvement not to have a <TableCell> component and instead inline its logic into the parent component.

Upewnij się, że parametry kaskadowe zostały naprawioneEnsure cascading parameters are fixed

<CascadingValue>Składnik ma opcjonalny parametr o nazwie IsFixed .The <CascadingValue> component has an optional parameter called IsFixed.

  • Jeśli IsFixed wartość jest false (domyślnie), a następnie każdy odbiorca wartości kaskadowej skonfiguruje subskrypcję do odbierania powiadomień o zmianach.If the IsFixed value is false (the default), then every recipient of the cascaded value sets up a subscription to receive change notifications. W takim przypadku każdy [CascadingParameter] jest znacznie droższy niż regularna [Parameter] ze względu na śledzenie subskrypcji.In this case, each [CascadingParameter] is substantially more expensive than a regular [Parameter] due to the subscription tracking.
  • Jeśli IsFixed wartość jest true (na przykład <CascadingValue Value="@someValue" IsFixed="true"> ), adresaci Pobiera wartość początkową, ale nie konfiguruje żadnej subskrypcji do odbierania aktualizacji.If the IsFixed value is true (for example, <CascadingValue Value="@someValue" IsFixed="true">), then receipients receive the initial value but do not set up any subscription to receive updates. W takim przypadku każdy [CascadingParameter] jest lekki i nie jest droższy od zwykłego [Parameter] .In this case, each [CascadingParameter] is lightweight and no more expensive than a regular [Parameter].

Tak, gdzie to możliwe, należy używać IsFixed="true" na wartościach kaskadowych.So wherever possible, you should use IsFixed="true" on cascaded values. Można to zrobić zawsze, gdy wartość jest podawana nie zmienia się w czasie.You can do this whenever the value being supplied doesn't change over time. We wspólnym wzorcu, w którym składnik przechodzi this jako wartość kaskadowo, należy użyć IsFixed="true" :In the common pattern where a component passes this as a cascaded value, you should use IsFixed="true":

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

Jest to bardzo istotna różnica, jeśli istnieje duża liczba innych składników, które otrzymują kaskadową wartość.This makes a huge difference if there are a large number of other components that receive the cascaded value. Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor kaskadowe wartości i parametry.For more information, see ASP.NET Core Blazor kaskadowe wartości i parametry.

Unikaj korzystając atrybutów z CaptureUnmatchedValuesAvoid attribute splatting with CaptureUnmatchedValues

Składniki mogą wybrać otrzymywanie niedopasowanych wartości parametrów przy użyciu CaptureUnmatchedValues flagi:Components can elect to receive "unmatched" parameter values using the CaptureUnmatchedValues flag:

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

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

Takie podejście umożliwia przechodzenie przez dowolne dodatkowe atrybuty do elementu.This approach allows passing through arbitrary additional attributes to the element. Jednak jest on również dość kosztowny, ponieważ moduł renderujący musi:However, it is also quite expensive because the renderer must:

  • Dopasowuje wszystkie podane parametry do zestawu znanych parametrów w celu skompilowania słownika.Match all of the supplied parameters against the set of known parameters to build a dictionary.
  • Śledź, jak wiele kopii tego samego atrybutu zastępuje siebie nawzajem.Keep track of how multiple copies of the same attribute overwrite each other.

Korzystaj z bezpłatnych CaptureUnmatchedValues składników niezwiązanych z wydajnością, takich jak te, które nie są często powtarzane.Feel free to use CaptureUnmatchedValues on non-performance-critical components, such as ones that are not repeated frequently. Jednak w przypadku składników, które są renderowane na dużą skalę, takich jak każdy element na dużej liście lub w komórkach w siatce, spróbuj uniknąć korzystając atrybutu.However for components that render at scale, such as each items in a large list or cells in a grid, try to avoid attribute splatting.

Aby uzyskać więcej informacji, zobacz Tworzenie i używanie Razor składników ASP.NET Core.For more information, see Tworzenie i używanie Razor składników ASP.NET Core.

Zaimplementuj SetParametersAsync ręcznieImplement SetParametersAsync manually

Jednym z głównych aspektów narzutu na składnik renderowania jest zapisanie przychodzących wartości parametrów do [Parameter] właściwości.One of the main aspects of the per-component rendering overhead is writing incoming parameter values to the [Parameter] properties. Aby to zrobić, moduł renderowania musi używać odbicia.The renderer has to use reflection to do this. Mimo że jest to nieco zoptymalizowane, brak obsługi JIT w środowisku uruchomieniowym webassembly nakłada limity.Even though this is somewhat optimized, the absence of JIT support on the WebAssembly runtime imposes limits.

W niektórych ekstremalnych przypadkach warto uniknąć odbicia i zaimplementować własną logikę ustawienia parametru ręcznie.In some extreme cases, you may wish to avoid the reflection and implement your own parameter setting logic manually. Może to być stosowane w przypadku:This may be applicable when:

  • Masz składnik, który renderuje bardzo często (na przykład, w interfejsie użytkownika znajdują się setki lub tysiące kopii tego elementu).You have a component that renders extremely often (for example, there are hundreds or thousands of copies of it in the UI).
  • Akceptuje wiele parametrów.It accepts many parameters.
  • Okaże się, że narzuty odbioru parametrów mają zauważalny wpływ na czas odpowiedzi interfejsu użytkownika.You find that the overhead of receiving parameters has an observable impact on UI responsiveness.

W takich przypadkach można przesłonić metodę wirtualną składnika SetParametersAsync i wdrożyć własną logikę specyficzną dla danego składnika.In these cases, you can override the component's virtual SetParametersAsync method and implement your own component-specific logic. W poniższym przykładzie zamierzone jest uniknięcie przeszukiwania słownika:The following example deliberately avoids any dictionary lookups:

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

W poprzednim kodzie zwracające klasę bazową SetParametersAsync działają normalne metody cyklu życia bez ponownego przypisywania parametrów.In the preceding code, returning the base class SetParametersAsync runs the normal lifecycle methods without assigning parameters again.

Jak widać w powyższym kodzie, zastępowanie SetParametersAsync i dostarczanie logiki niestandardowej jest skomplikowane i pracochłonne, dlatego nie zalecamy ogólnie tego podejścia.As you can see in the preceding code, overriding SetParametersAsync and supplying custom logic is complicated and laborious, so we don't recommend this approach in general. W skrajnych przypadkach może zwiększyć wydajność renderowania o 20-25%, ale rozwiązanie to należy wziąć pod uwagę tylko w scenariuszach wymienionych powyżej.In extreme cases, it can improve rendering performance by 20-25%, but you should only consider this approach in the scenarios listed earlier.

Nie Wyzwalaj zbyt szybko zdarzeńDon't trigger events too rapidly

Niektóre zdarzenia przeglądarki są niezwykle często wyzwalane, na przykład onmousemove i onscroll , które mogą być uruchamiane dziesiątki lub setki razy na sekundę.Some browser events fire extremely frequently, for example onmousemove and onscroll, which can fire tens or hundreds of times per second. W większości przypadków nie trzeba często wykonywać aktualizacji interfejsu użytkownika.In most cases, you don't need to perform UI updates this frequently. Jeśli użytkownik spróbuje to zrobić, może to spowodować szkody czas odpowiedzi interfejsu użytkownika lub nadmierne wykorzystanie procesora CPU.If you try to do so, you may harm UI responsiveness or consume excessive CPU time.

Zamiast używać natywnych @onmousemove lub @onscroll zdarzeń, warto użyć kodu js Interop do zarejestrowania wywołania zwrotnego, które jest generowane rzadziej.Rather than using native @onmousemove or @onscroll events, you may prefer to use JS interop to register a callback that fires less frequently. Na przykład poniższy składnik ( MyComponent.razor ) wyświetla pozycję myszy, ale tylko aktualizuje się co najwyżej raz na 500 MS:For example, the following component (MyComponent.razor) displays the position of the mouse but only updates at most once every 500 ms:

@inject IJSRuntime JS
@implements IDisposable

<h1>@message</h1>

<div @ref="myMouseMoveElement" style="border:1px dashed red;height:200px;">
    Move mouse here
</div>

@code {
    ElementReference myMouseMoveElement;
    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; // Only notify every 500 ms
            await JS.InvokeVoidAsync("onThrottledMouseMove", 
                myMouseMoveElement, selfReference, minInterval);
        }
    }

    public void Dispose() => selfReference?.Dispose();
}

Odpowiedni kod JavaScript, który może być umieszczony na index.html stronie lub załadowany jako moduł ES6, rejestruje rzeczywisty odbiornik zdarzeń dom.The corresponding JavaScript code, which can be placed in the index.html page or loaded as an ES6 module, registers the actual DOM event listener. W tym przykładzie odbiornik zdarzeń używa throttle funkcji Lodash , aby ograniczyć szybkość wywołań:In this example, the event listener uses Lodash's throttle function to limit the rate of invocations:

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

Ta technika może być jeszcze bardziej ważna dla Blazor Server , ponieważ każde wywołanie zdarzenia obejmuje dostarczenie komunikatu przez sieć.This technique can be even more important for Blazor Server, since each event invocation involves delivering a message over the network. Jest to przydatne w przypadku, gdy Blazor WebAssembly może znacznie zmniejszyć ilość pracy renderowania.It's valuable for Blazor WebAssembly because it can greatly reduce the amount of rendering work.

Optymalizuj szybkość międzyoperacyjności kodu JavaScriptOptimize JavaScript interop speed

Wywołania między programami .NET i JavaScript obejmują kilka dodatkowych kosztów, ponieważ:Calls between .NET and JavaScript involve some additional overhead because:

  • Domyślnie wywołania są asynchroniczne.By default, calls are asynchronous.
  • Domyślnie parametry i zwracane wartości są serializowane w formacie JSON.By default, parameters and return values are JSON-serialized. Ma to na celu zapewnienie łatwego w zrozumieniu mechanizmu konwersji między typami .NET i JavaScript.This is to provide an easy-to-understand conversion mechanism between .NET and JavaScript types.

Ponadto Blazor Server te wywołania są przesyłane przez sieć.Additionally on Blazor Server, these calls are passed across the network.

Unikaj nadmiernie szczegółowych wywołańAvoid excessively fine-grained calls

Ponieważ każde wywołanie wiąże się z pewnym obciążeniem, może być cenne, aby zmniejszyć liczbę wywołań.Since each call involves some overhead, it can be valuable to reduce the number of calls. Rozważmy następujący kod, który przechowuje kolekcję elementów w localStorage sklepie przeglądarki:Consider the following code, which stores a collection of items in the browser's localStorage store:

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
    foreach (var item in items)
    {
        await JS.InvokeVoidAsync("localStorage.setItem", item.Id, 
            JsonSerializer.Serialize(item));
    }
}

W powyższym przykładzie jest to oddzielne wywołanie międzyoperacyjna JS dla każdego elementu.The preceding example makes a separate JS interop call for each item. Zamiast tego, następujące podejście zmniejsza międzyoperacyjność elementu JS do pojedynczego wywołania:Instead, the following approach reduces the JS interop to a single call:

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
    await JS.InvokeVoidAsync("storeAllInLocalStorage", items);
}

Odpowiednia funkcja języka JavaScript zdefiniowana w następujący sposób:The corresponding JavaScript function defined as follows:

function storeAllInLocalStorage(items) {
  items.forEach(item => {
    localStorage.setItem(item.id, JSON.stringify(item));
  });
}

W przypadku Blazor WebAssembly , zwykle jest to kwestia tylko wtedy, gdy wykonujesz dużą liczbę wywołań międzyoperacyjnych w języku js.For Blazor WebAssembly, this usually only matters if you're making a large number of JS interop calls.

Rozważ możliwość wykonywania wywołań synchronicznychConsider making synchronous calls

Wywołania międzyoperacyjne JavaScript są domyślnie asynchroniczne, bez względu na to, czy kod jest wywoływany synchronicznie, czy asynchronicznie.JavaScript interop calls are asynchronous by default, regardless of whether the code being called is synchronous or asynchronous. Ma to na celu zapewnienie, że składniki są zgodne z systemami Blazor WebAssembly i Blazor Server .This is to ensure components are compatible with both Blazor WebAssembly and Blazor Server. W systemie Blazor Server wszystkie wywołania międzyoperacyjne języka JavaScript muszą być asynchroniczne, ponieważ są wysyłane za pośrednictwem połączenia sieciowego.On Blazor Server, all JavaScript interop calls must be asynchronous because they are sent over a network connection.

Jeśli masz pewność, że aplikacja została kiedykolwiek uruchomiona tylko w programie Blazor WebAssembly , możesz wybrać opcję wykonywania synchronicznych wywołań w języku JavaScript.If you know for certain that your app only ever runs on Blazor WebAssembly, you can choose to make synchronous JavaScript interop calls. Ta funkcja ma nieco mniej obciążenia niż wywołania asynchroniczne i może powodować mniejszą liczbę cykli renderowania, ponieważ nie ma stanu pośredniego podczas oczekiwania na wyniki.This has slightly less overhead than making asynchronous calls and can result in fewer render cycles because there is no intermediate state while awaiting results.

Aby wykonać synchroniczne wywołanie z platformy .NET do języka JavaScript, Rzutowanie IJSRuntime na IJSInProcessRuntime :To make a synchronous call from .NET to JavaScript, cast IJSRuntime to IJSInProcessRuntime:

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

Podczas pracy z programem IJSObjectReference można wykonać wywołanie synchroniczne przez rzutowanie na IJSInProcessObjectReference .When working with IJSObjectReference, you can make a synchronous call by casting to IJSInProcessObjectReference.

Aby wykonać synchroniczne wywołanie z JavaScript do .NET, użyj DotNet.invokeMethod zamiast DotNet.invokeMethodAsync .To make a synchronous call from JavaScript to .NET, use DotNet.invokeMethod instead of DotNet.invokeMethodAsync.

Wywołania synchroniczne działają, jeśli:Synchronous calls work if:

  • Aplikacja działa w systemie Blazor WebAssembly , nie Blazor Server .The app is running on Blazor WebAssembly, not Blazor Server.
  • Wywołana funkcja zwraca wartość synchronicznie (nie jest to async Metoda i nie zwraca platformy .NET Task ani JavaScript Promise ).The called function returns a value synchronously (it isn't an async method and doesn't return a .NET Task or JavaScript Promise).

Aby uzyskać więcej informacji, zobacz Wywoływanie funkcji języka JavaScript z metod .NET w ASP.NET Core Blazor.For more information, see Wywoływanie funkcji języka JavaScript z metod .NET w ASP.NET Core Blazor.

Rozważ utworzenie nieskierowanych wywołańConsider making unmarshalled calls

W przypadku uruchamiania w systemie Blazor WebAssembly można wykonać nieskierowanie wywołania z platformy .NET do języka JavaScript.When running on Blazor WebAssembly, it's possible to make unmarshalled calls from .NET to JavaScript. Są to wywołania synchroniczne, które nie wykonują serializacji JSON argumentów lub zwracanych wartości.These are synchronous calls that don't perform JSON serialization of arguments or return values. Wszystkie aspekty związane z zarządzaniem pamięcią i tłumaczeniami między środowiskami .NET i JavaScript są pozostawiane do dewelopera.All aspects of memory management and translations between .NET and JavaScript representations are left up to the developer.

Ostrzeżenie

Podczas używania IJSUnmarshalledRuntime ma najmniejsze obciążenie związane z interfejsem js międzyoperacyjności, interfejsy API języka JavaScript wymagane do współdziałania z tymi interfejsy API są obecnie nieudokumentowane i mogą ulegać zmianom w przyszłych wersjach.While using IJSUnmarshalledRuntime has the least overhead of the JS interop approaches, the JavaScript APIs required to interact with these APIs are currently undocumented and subject to breaking changes in future releases.

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

Minimalizuj rozmiar pobierania aplikacjiMinimize app download size

Przycinanie języka pośredniego (IL)Intermediate Language (IL) trimming

Przycinanie nieużywanych zestawów z Blazor WebAssembly aplikacji zmniejsza rozmiar aplikacji przez usunięcie nieużywanego kodu w danych binarnych aplikacji.Trimming unused assemblies from a Blazor WebAssembly app reduces the app's size by removing unused code in the app's binaries. Aby uzyskać więcej informacji, zobacz Skonfiguruj element dostosowujący dla ASP.NET Core Blazor.For more information, see Skonfiguruj element dostosowujący dla ASP.NET Core Blazor.

Tworzenie łączy języka pośredniego (IL)Intermediate Language (IL) linking

Łączenie Blazor WebAssembly aplikacji zmniejsza rozmiar aplikacji przez przycinanie nieużywanego kodu w plikach binarnych aplikacji.Linking a Blazor WebAssembly app reduces the app's size by trimming unused code in the app's binaries. Domyślnie konsolidator języka pośredniego (IL) jest włączony tylko w przypadku kompilowania w Release konfiguracji.By default, the Intermediate Language (IL) Linker is only enabled when building in Release configuration. Aby z tego skorzystać, Opublikuj aplikację do wdrożenia przy użyciu dotnet publish polecenia z opcją -c |--Configuration ustawioną na Release :To benefit from this, publish the app for deployment using the dotnet publish command with the -c|--configuration option set to Release:

dotnet publish -c Release

Użyj System.Text.JsnaUse System.Text.Json

Blazorimplementacja elementu webinterop w języku JS polega na tym System.Text.Json , że jest to biblioteka serializacji JSON o wysokiej wydajności z alokacją małej ilości pamięci.Blazor's JS interop implementation relies on System.Text.Json, which is a high-performance JSON serialization library with low memory allocation. Użycie nie powoduje, że System.Text.Json rozmiar ładunku aplikacji jest większy niż dodanie co najmniej jednej alternatywnej biblioteki JSON.Using System.Text.Json doesn't result in additional app payload size over adding one or more alternate JSON libraries.

Aby uzyskać wskazówki dotyczące migracji, zobacz Jak przeprowadzić migrację z Newtonsoft.Json do programu System.Text.Json .For migration guidance, see How to migrate from Newtonsoft.Json to System.Text.Json.

Zestawy ładowania z opóźnieniemLazy load assemblies

Ładuj zestawy w czasie wykonywania, gdy zestawy są wymagane przez trasę.Load assemblies at runtime when the assemblies are required by a route. Aby uzyskać więcej informacji, zobacz Zestawy ładowania z opóźnieniem w ASP.NET Core Blazor WebAssembly.For more information, see Zestawy ładowania z opóźnieniem w ASP.NET Core Blazor WebAssembly.

KompresjaCompression

Po Blazor WebAssembly opublikowaniu aplikacji dane wyjściowe są kompresowane statycznie podczas publikowania, aby zmniejszyć rozmiar aplikacji i usunąć obciążenie dla kompresji w czasie wykonywania.When a Blazor WebAssembly app is published, the output is statically compressed during publish to reduce the app's size and remove the overhead for runtime compression. Blazor opiera się na serwerze w celu przeprowadzenia negotation zawartości i obkompresji plików skompresowanych statycznie.Blazor relies on the server to perform content negotation and serve statically-compressed files.

Po wdrożeniu aplikacji Sprawdź, czy aplikacja obsługuje skompresowane pliki.After an app is deployed, verify that the app serves compressed files. Zbadaj kartę Sieć w Narzędzia deweloperskie przeglądarki i sprawdź, czy pliki są obsługiwane przez program Content-Encoding: br lub Content-Encoding: gz .Inspect the Network tab in a browser's Developer Tools and verify that the files are served with Content-Encoding: br or Content-Encoding: gz. Jeśli host nie obsługuje skompresowanych plików, postępuj zgodnie z instrukcjami w temacie Hostowanie i wdrażanie ASP.NET Core Blazor WebAssembly .If the host isn't serving compressed files, follow the instructions in Hostowanie i wdrażanie ASP.NET Core Blazor WebAssembly.

Wyłącz nieużywane funkcjeDisable unused features

Blazor WebAssemblyśrodowisko uruchomieniowe obejmuje następujące funkcje platformy .NET, które można wyłączyć, jeśli aplikacja nie wymaga ich dla mniejszego rozmiaru ładunku:Blazor WebAssembly's runtime includes the following .NET features that can be disabled if the app doesn't require them for a smaller payload size:

  • Plik danych jest uwzględniany w celu poprawnego wprowadzania informacji o strefie czasowej.A data file is included to make timezone information correct. Jeśli aplikacja nie wymaga tej funkcji, rozważ wyłączenie jej przez ustawienie właściwości programu BlazorEnableTimeZoneSupport MSBuild w pliku projektu aplikacji na false :If the app doesn't require this feature, consider disabling it by setting the BlazorEnableTimeZoneSupport MSBuild property in the app's project file to false:

    <PropertyGroup>
      <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
    </PropertyGroup>
    
  • Domyślnie Blazor WebAssembly zasoby globalizacji są wymagane do wyświetlania wartości, takich jak daty i waluta, w kulturze użytkownika.By default, Blazor WebAssembly carries globalization resources required to display values, such as dates and currency, in the user's culture. Jeśli aplikacja nie wymaga lokalizacji, można skonfigurować aplikację do obsługi niezmiennej kultury, która jest oparta na en-US kulturze:If the app doesn't require localization, you may configure the app to support the invariant culture, which is based on the en-US culture:

    <PropertyGroup>
      <InvariantGlobalization>true</InvariantGlobalization>
    </PropertyGroup>
    
  • Informacje o sortowaniu są uwzględniane w celu StringComparison.InvariantCultureIgnoreCase poprawnego działania interfejsów API.Collation information is included to make APIs such as StringComparison.InvariantCultureIgnoreCase work correctly. Jeśli masz pewność, że aplikacja nie wymaga danych sortowania, rozważ wyłączenie jej przez ustawienie BlazorWebAssemblyPreserveCollationData Właściwości programu MSBuild w pliku projektu aplikacji na false :If you're certain that the app doesn't require the collation data, consider disabling it by setting the BlazorWebAssemblyPreserveCollationData MSBuild property in the app's project file to false:

    <PropertyGroup>
      <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
    </PropertyGroup>