zaawansowane scenariusze ASP.NET Core Blazor (tworzenie drzewa renderowania)

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

W tym artykule opisano zaawansowany scenariusz ręcznego tworzenia Blazor drzew renderowanych za pomocą polecenia RenderTreeBuilder.

Ostrzeżenie

Tworzenie składników RenderTreeBuilder jest zaawansowanym scenariuszem. Źle sformułowany składnik (na przykład nieujawiony tag znaczników) może spowodować niezdefiniowane zachowanie. Niezdefiniowane zachowanie obejmuje uszkodzone renderowanie zawartości, utratę funkcji aplikacji i zabezpieczenia naruszone.

Ręczne tworzenie drzewa renderowania (RenderTreeBuilder)

RenderTreeBuilder Udostępnia metody manipulowania składnikami i elementami, w tym ręczne kompilowanie składników w kodzie języka C#.

Rozważmy następujący PetDetails składnik, który można ręcznie renderować w innym składniku.

PetDetails.razor:

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

@code
{
    [Parameter]
    public string? PetDetailsQuote { get; set; }
}

W poniższym BuiltContent składniku pętla w metodzie CreateComponent generuje trzy PetDetails składniki.

W RenderTreeBuilder metodach z numerem sekwencji numery sekwencji to numery wierszy kodu źródłowego. Algorytm Blazor różnicy opiera się na numerach sekwencji odpowiadających odrębnym wierszom kodu, a nie odrębnym wywołaniom wywołań. Podczas tworzenia składnika za pomocą RenderTreeBuilder metod zakoduj argumenty dla numerów sekwencji. Użycie obliczenia lub licznika do wygenerowania numeru sekwencji może prowadzić do niskiej wydajności. Aby uzyskać więcej informacji, zobacz Sekcję Numery sekwencji odnoszą się do numerów wierszy kodu, a nie z kolejnością wykonywania.

BuiltContent.razor:

@page "/built-content"

<PageTitle>Built Content</PageTitle>

<h1>Built Content Example</h1>

<div>
    @CustomRender
</div>

<button @onclick="RenderComponent">
    Create three Pet Details components
</button>

@code {
    private RenderFragment? CustomRender { get; set; }

    private RenderFragment CreateComponent() => builder =>
    {
        for (var i = 0; i < 3; i++) 
        {
            builder.OpenComponent(0, typeof(PetDetails));
            builder.AddAttribute(1, "PetDetailsQuote", "Someone's best friend!");
            builder.CloseComponent();
        }
    };

    private void RenderComponent()
    {
        CustomRender = CreateComponent();
    }
}
@page "/built-content"

<h1>Build a component</h1>

<div>
    @CustomRender
</div>

<button @onclick="RenderComponent">
    Create three Pet Details components
</button>

@code {
    private RenderFragment? CustomRender { get; set; }

    private RenderFragment CreateComponent() => builder =>
    {
        for (var i = 0; i < 3; i++) 
        {
            builder.OpenComponent(0, typeof(PetDetails));
            builder.AddAttribute(1, "PetDetailsQuote", "Someone's best friend!");
            builder.CloseComponent();
        }
    };

    private void RenderComponent()
    {
        CustomRender = CreateComponent();
    }
}

Ostrzeżenie

Typy w Microsoft.AspNetCore.Components.RenderTree programie umożliwiają przetwarzanie wyników operacji renderowania. Są to wewnętrzne szczegóły implementacji Blazor platformy. Te typy powinny być uważane za niestabilne i mogą ulec zmianie w przyszłych wersjach.

Numery sekwencji odnoszą się do numerów wierszy kodu, a nie kolejności wykonywania

Razor pliki składników (.razor) są zawsze kompilowane. Wykonywanie skompilowanego kodu ma potencjalną przewagę nad interpretowaniem kodu, ponieważ krok kompilacji, który daje skompilowany kod, może służyć do wstrzykiwania informacji, które zwiększają wydajność aplikacji w czasie wykonywania.

Kluczowym przykładem tych ulepszeń są numery sekwencji. Numery sekwencji wskazują środowisko uruchomieniowe, z którego pochodzą dane wyjściowe, z których pochodzą odrębne i uporządkowane wiersze kodu. Środowisko uruchomieniowe używa tych informacji do generowania wydajnych różnic drzewa w czasie liniowym, co jest znacznie szybsze niż zwykle w przypadku ogólnego algorytmu różnic drzewa.

Rozważmy następujący Razor plik składnika (.razor):

@if (someFlag)
{
    <text>First</text>
}

Second

Razor Powyższe znaczniki i zawartość tekstowa są kompilowane w kodzie języka C# podobnym do następującego:

if (someFlag)
{
    builder.AddContent(0, "First");
}

builder.AddContent(1, "Second");

Gdy kod jest wykonywany po raz pierwszy i someFlag ma truewartość , konstruktor otrzymuje sekwencję w poniższej tabeli.

Sequence Typ Data
0 Węzeł tekstowy First
1 Węzeł tekstowy Second

Wyobraź sobie, że staje false się i someFlag adiustacja jest renderowana ponownie. Tym razem konstruktor otrzymuje sekwencję w poniższej tabeli.

Sequence Typ Data
1 Węzeł tekstowy Second

Gdy środowisko uruchomieniowe wykonuje różnicę, zobaczy, że element w sekwencji 0 został usunięty, dlatego generuje następujący trywialny skrypt edycji z jednym krokiem:

  • Usuń pierwszy węzeł tekstowy.

Problem z programowym generowaniem numerów sekwencji

Załóżmy, że zamiast tego napisaliśmy następującą logikę konstruktora drzewa renderowania:

var seq = 0;

if (someFlag)
{
    builder.AddContent(seq++, "First");
}

builder.AddContent(seq++, "Second");

Pierwsze dane wyjściowe zostaną odzwierciedlone w poniższej tabeli.

Sequence Typ Data
0 Węzeł tekstowy First
1 Węzeł tekstowy Second

Ten wynik jest identyczny z poprzednim przypadkiem, więc nie istnieją żadne negatywne problemy. someFlag jest false w drugim renderowaniu, a dane wyjściowe są widoczne w poniższej tabeli.

Sequence Typ Data
0 Węzeł tekstowy Second

Tym razem algorytm różnicy widzi, że wystąpiły dwie zmiany. Algorytm generuje następujący skrypt edycji:

  • Zmień wartość pierwszego węzła tekstowego na Second.
  • Usuń drugi węzeł tekstowy.

Generowanie numerów sekwencji straciło wszystkie przydatne informacje o tym, gdzie if/else gałęzie i pętle były obecne w oryginalnym kodzie. Powoduje to różnice dwa razy więcej niż wcześniej.

Jest to banalny przykład. W bardziej realistycznych przypadkach ze złożonymi i głęboko zagnieżdżonym strukturami, a zwłaszcza w przypadku pętli, koszt wydajności jest zwykle wyższy. Zamiast natychmiast identyfikować bloki pętli lub gałęzie, które zostały wstawione lub usunięte, algorytm różnicy musi się powtarzać głęboko w drzewach renderowania. Zwykle skutkuje to tworzeniem dłuższych skryptów edycji, ponieważ algorytm różnicy jest błędnie poinformowany o tym, jak stare i nowe struktury odnoszą się do siebie nawzajem.

Wskazówki i wnioski

  • Wydajność aplikacji występuje w przypadku dynamicznego generowania numerów sekwencji.
  • Platforma nie może automatycznie tworzyć własnych numerów sekwencji w czasie wykonywania, ponieważ niezbędne informacje nie istnieją, chyba że zostaną przechwycone w czasie kompilacji.
  • Nie zapisuj długich bloków logiki implementowanych RenderTreeBuilder ręcznie. Preferuj .razor pliki i zezwól kompilatorowi na radzenie sobie z numerami sekwencji. Jeśli nie możesz uniknąć ręcznej RenderTreeBuilder logiki, podziel długie bloki kodu na mniejsze elementy opakowane w OpenRegion/CloseRegion wywołania. Każdy region ma własną oddzielną przestrzeń numerów sekwencji, więc można uruchomić ponownie z zera (lub dowolnej innej liczby) wewnątrz każdego regionu.
  • Jeśli numery sekwencji są zakodowane na stałe, algorytm różnicy wymaga tylko zwiększenia wartości liczb sekwencji. Początkowa wartość i luki są nieistotne. Jedną z uzasadnionych opcji jest użycie numeru wiersza kodu jako numeru sekwencji lub rozpoczęcie od zera i zwiększenie o te lub setki (lub dowolny preferowany interwał).
  • Blazor używa numerów sekwencji, podczas gdy inne struktury interfejsu użytkownika dzielące drzewa nie używają ich. Różnicowanie jest znacznie szybsze, gdy są używane numery sekwencji i Blazor ma zaletę kroku kompilacji, który automatycznie zajmuje się numerami sekwencji dla deweloperów tworzących .razor pliki.