Compartir vía


Escenarios avanzados de ASP.NET Core Blazor (construcción de árboles de representación)

Nota

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión de .NET 9 de este artículo.

En este artículo se describe el escenario avanzado para crear árboles de renderización de Blazor manualmente con RenderTreeBuilder.

Advertencia

El uso de RenderTreeBuilder para crear componentes es un escenario avanzado. Un componente con formato incorrecto (por ejemplo, una etiqueta de marcado sin cerrar) puede dar como resultado un comportamiento indefinido. Un comportamiento no definido incluye la representación de contenido roto, la pérdida de características de la aplicación y vulneraciones de la seguridad.

Creación manual de un árbol de representación (RenderTreeBuilder)

RenderTreeBuilder proporciona métodos para manipular componentes y elementos, incluida la compilación manual de componentes en código de C#.

Ten en cuenta el siguiente componente PetDetails, que se puede integrar manualmente en otro componente.

PetDetails.razor:

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

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

En el siguiente componente BuiltContent, el bucle en el método CreateComponent genera tres componentes PetDetails.

En métodos RenderTreeBuilder con un número de secuencia, los números de secuencia son números de línea de código fuente. El algoritmo de diferencia Blazor se basa en los números de secuencia correspondientes a líneas de código distintas, no a invocaciones de llamada distintas. Al crear un componente con métodos RenderTreeBuilder, codifica los argumentos para los números de secuencia. El uso de un cálculo o un contador para generar el número de secuencia puede dar lugar a un rendimiento deficiente. Para obtener más información, consulta la sección Los números de secuencia se relacionan con los números de línea de código y no con el orden de ejecución.

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

Advertencia

Los tipos en Microsoft.AspNetCore.Components.RenderTree permiten el procesamiento de los resultados de las operaciones de representación. Estos son los detalles internos de la implementación del marco Blazor. Estos tipos se deben considerar inestables y deben estar sujetos a cambios en versiones futuras.

Los números de secuencia se relacionan con los números de línea de código y no con el orden de ejecución

Los archivos de componentes Razor (.razor) siempre se compilan. Ejecutar código compilado tiene una ventaja potencial sobre interpretarlo, porque el paso de la compilación que suspende el código compilado se puede usar para insertar información que mejore el rendimiento de las aplicaciones en tiempo de ejecución.

Un ejemplo clave de estas mejoras implica números de secuencia. Los números de secuencia indican al runtime qué resultados proceden a partir de qué líneas de código distintas y ordenadas. El runtime utiliza esta información con el fin de generar diferencias de árbol eficientes en tiempo lineal, que es mucho más rápido de lo que normalmente es posible para un algoritmo general de diferencia de árboles.

Considera el siguiente componente Razor (.razor):

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

Second

El marcado Razor y el contenido de texto anteriores se compilan en un código C# similar al siguiente:

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

builder.AddContent(1, "Second");

Cuando el código se ejecuta por primera vez y someFlag es true, el generador recibe la secuencia en la tabla siguiente.

Secuencia Tipo Datos
0 Nodo de texto First
1 Nodo de texto Second

Imagina que someFlag se convierte en false y que el marcado se representa de nuevo. Esta vez, el generador recibe la secuencia en la tabla siguiente.

Secuencia Tipo Datos
1 Nodo de texto Second

Cuando el entorno de ejecución realiza una diferencia, verifica que se haya eliminado el elemento en la secuencia 0, por lo que genera el siguiente script de edición trivial en un solo paso:

  • Quita el primer nodo de texto.

Problema con la generación de números de secuencia mediante programación

Imagina que se ha escrito la siguiente lógica del generador de árboles de representación:

var seq = 0;

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

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

La primera salida se refleja en la tabla siguiente.

Secuencia Tipo Datos
0 Nodo de texto First
1 Nodo de texto Second

Este resultado es idéntico al del caso anterior, por lo que no existen incidencias negativas. someFlag es false en la segunda representación y la salida se ve en la tabla siguiente.

Secuencia Tipo Datos
0 Nodo de texto Second

Esta vez, el algoritmo de diferencia verifica que se produzcan dos cambios. El algoritmo genera el siguiente script de edición:

  • Cambia el valor del primer nodo de texto a Second.
  • Quita el segundo nodo de texto.

La generación de los números de secuencia ha perdido toda la información útil sobre el lugar en el que las ramas if/else y los bucles estaban presentes en el código original. Esto da como resultado una diferencia dos veces más larga que antes.

Este es un ejemplo trivial. En casos más realistas con estructuras complejas y profundamente anidadas, y especialmente con bucles, el coste de rendimiento suele ser mayor. En lugar de identificar inmediatamente los bloques o ramas de bucle que se han insertado o quitado, el algoritmo de diferencia tiene que recorrer en profundidad los árboles de representación. Esto suele dar lugar a tener que compilar scripts de edición más largos, ya que el algoritmo de diferencia está desinformado sobre cómo se relacionan entre sí las estructuras antiguas y nuevas.

Guía y conclusiones

  • El rendimiento de la aplicación se ve afectado si los números de secuencia se generan dinámicamente.
  • El marco no puede generar sus propios números de secuencia automáticamente en runtime porque la información necesaria no existe a menos que se capture en tiempo de compilación.
  • No escribas bloques grandes de lógica RenderTreeBuilder implementada de forma manual. Da preferencia a los archivos .razor y permite que el compilador trate los números de secuencia. Si no puedes evitar la lógica RenderTreeBuilder manual, divide los bloques grandes de código en fragmentos más pequeños encapsulados en llamadas OpenRegion/CloseRegion. Cada región tiene su propio espacio independiente de números de secuencia, por lo que puedes reiniciar desde cero (o cualquier otro número arbitrario) en cada región.
  • Si los números de secuencia están codificados, el algoritmo de diferencia solo requiere que los números de secuencia aumenten de valor. El valor inicial y los intervalos son irrelevantes. Una opción legítima es usar el número de línea de código como el número de secuencia, o comenzar a partir de cero y aumentar en unos o cientos (o cualquier intervalo preferido).
  • Para los bucles, los números de secuencia deben aumentar en el código fuente, no en términos de comportamiento en tiempo de ejecución. El hecho de que, en tiempo de ejecución, los números se repiten es la forma en que el sistema de diferenciación se da cuenta de que está en un bucle.
  • Blazor utiliza los números de secuencia, mientras que otros marcos de la interfaz de usuario de diferencia de árboles no. La comparación es mucho más rápida cuando se utilizan números de secuencia, y la ventaja de Blazor es un paso de compilación que trata los números de secuencia automáticamente para desarrolladores que crean archivos .razor.