Blazor ASP.NET principali scenari avanzati (costruzione dell'albero di rendering)

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Questo articolo descrive lo scenario avanzato per la compilazione Blazor manuale degli alberi di rendering con RenderTreeBuilder.

Avviso

L'uso di RenderTreeBuilder per creare componenti è uno scenario avanzato. Un componente in formato non valido ,ad esempio un tag di markup non chiuso, può comportare un comportamento non definito. Il comportamento non definito include il rendering dei contenuti interrotti, la perdita di funzionalità dell'app e la sicurezza compromessa.

Creare manualmente un albero di rendering (RenderTreeBuilder)

RenderTreeBuilder fornisce metodi per la modifica di componenti ed elementi, inclusi la compilazione manuale dei componenti nel codice C#.

Si consideri il componente seguente PetDetails , che può essere sottoposto a rendering manuale in un altro componente.

PetDetails.razor:

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

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

Nel componente seguente BuiltContent il ciclo nel CreateComponent metodo genera tre PetDetails componenti.

Nei RenderTreeBuilder metodi con un numero di sequenza i numeri di sequenza sono numeri di riga del codice sorgente. L'algoritmo Blazor di differenza si basa sui numeri di sequenza corrispondenti a righe di codice distinte, non chiamate di chiamata distinte. Quando si crea un componente con RenderTreeBuilder metodi, impostare come hardcode gli argomenti per i numeri di sequenza. L'uso di un calcolo o di un contatore per generare il numero di sequenza può causare prestazioni scarse. Per altre informazioni, vedere la sezione Numeri di sequenza correlati ai numeri di riga di codice e non all'ordine di esecuzione.

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"

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

Avviso

I tipi in Microsoft.AspNetCore.Components.RenderTree consentono l'elaborazione dei risultati delle operazioni di rendering. Questi sono i dettagli interni dell'implementazione del Blazor framework. Questi tipi devono essere considerati instabili e soggetti a modifiche nelle versioni future.

I numeri di sequenza sono correlati ai numeri di riga di codice e non all'ordine di esecuzione

Razor i file dei componenti (.razor) vengono sempre compilati. L'esecuzione di codice compilato presenta un potenziale vantaggio rispetto all'interpretazione del codice perché il passaggio di compilazione che produce il codice compilato può essere usato per inserire informazioni che migliorano le prestazioni dell'app in fase di esecuzione.

Un esempio chiave di questi miglioramenti riguarda i numeri di sequenza. I numeri di sequenza indicano al runtime gli output provenienti da quali righe di codice distinte e ordinate. Il runtime usa queste informazioni per generare differenze di albero efficienti in tempo lineare, che è molto più veloce di quanto normalmente sia possibile per un algoritmo diff dell'albero generale.

Prendere in considerazione il file di componente seguente Razor (.razor):

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

Second

Il markup e il contenuto di testo precedenti Razor vengono compilati in codice C# simile al seguente:

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

builder.AddContent(1, "Second");

Quando il codice viene eseguito per la prima volta e someFlag è true, il generatore riceve la sequenza nella tabella seguente.

Sequenza Type Dati
0 Nodo testo First
1 Nodo testo Secondo

Si supponga che someFlag diventi false e che venga nuovamente eseguito il rendering del markup. Questa volta, il generatore riceve la sequenza nella tabella seguente.

Sequenza Type Dati
1 Nodo testo Secondo

Quando il runtime esegue una differenza, rileva che l'elemento in fase di sequenza 0 è stato rimosso, quindi genera lo script di modifica semplice seguente con un singolo passaggio:

  • Rimuovere il primo nodo di testo.

Problema con la generazione di numeri di sequenza a livello di codice

Si supponga invece di aver scritto la logica di generatore di alberi di rendering seguente:

var seq = 0;

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

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

Il primo output si riflette nella tabella seguente.

Sequenza Type Dati
0 Nodo testo First
1 Nodo testo Secondo

Questo risultato è identico al caso precedente, quindi non esistono problemi negativi. someFlag è false nel secondo rendering e l'output viene visualizzato nella tabella seguente.

Sequenza Type Dati
0 Nodo testo Secondo

Questa volta, l'algoritmo diff rileva che sono state apportate due modifiche. L'algoritmo genera lo script di modifica seguente:

  • Modificare il valore del primo nodo di testo in Second.
  • Rimuovere il secondo nodo di testo.

La generazione dei numeri di sequenza ha perso tutte le informazioni utili sulla posizione if/else dei rami e dei cicli presenti nel codice originale. Ciò comporta un diff due volte fino a quando prima.

Questo è un esempio semplice. In casi più realistici con strutture complesse e profondamente annidate, e soprattutto con cicli, il costo delle prestazioni è in genere superiore. Invece di identificare immediatamente quali blocchi di ciclo o rami sono stati inseriti o rimossi, l'algoritmo diff deve entrare in profondità negli alberi di rendering. Ciò comporta in genere la creazione di script di modifica più lunghi perché l'algoritmo diff non è corretto sul modo in cui le strutture vecchie e nuove sono correlate tra loro.

Linee guida e conclusioni

  • Le prestazioni dell'app subiscono un problema se i numeri di sequenza vengono generati dinamicamente.
  • Le informazioni necessarie non esistono per consentire al framework di generare automaticamente i numeri di sequenza in fase di esecuzione, a meno che le informazioni non vengano acquisite in fase di compilazione.
  • Non scrivere blocchi lunghi di logica implementata RenderTreeBuilder manualmente. Preferisce .razor i file e consentire al compilatore di gestire i numeri di sequenza. Se non è possibile evitare la logica manuale RenderTreeBuilder , suddividere blocchi lunghi di codice in parti più piccole di cui è stato eseguito il wrapping nelle OpenRegion/CloseRegion chiamate. Ogni area ha uno spazio separato di numeri di sequenza, quindi è possibile riavviare da zero (o da qualsiasi altro numero arbitrario) all'interno di ogni area.
  • Se i numeri di sequenza sono hardcoded, l'algoritmo diff richiede solo che i numeri di sequenza aumentino di valore. Il valore iniziale e le lacune sono irrilevanti. Un'opzione legittima consiste nell'usare il numero di riga del codice come numero di sequenza oppure iniziare da zero e aumentare di uno o centinaia (o qualsiasi intervallo preferito).
  • Per i cicli, i numeri di sequenza dovrebbero aumentare nel codice sorgente, non in termini di comportamento di runtime. Il fatto che, in fase di esecuzione, i numeri si ripetono è il modo in cui il sistema diffing si rende conto di essere in un ciclo.
  • Blazor usa i numeri di sequenza, mentre altri framework dell'interfaccia utente ad albero non li usano. Il diffing è molto più veloce quando vengono usati i numeri di sequenza e Blazor ha il vantaggio di un passaggio di compilazione che gestisce automaticamente i numeri di sequenza per gli sviluppatori che creano .razor file.