Compartilhar via


Cenários avançados de ASP.NET Core Blazor (construção de árvore de renderização)

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Aviso

Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Este artigo descreve o cenário avançado para criar árvores de renderização Blazor manualmente com RenderTreeBuilder.

Aviso

O uso de RenderTreeBuilder para criar componentes é um cenário avançado. Um componente malformado (por exemplo, uma marca não revelada) pode resultar em um comportamento indefinido. O comportamento indefinido inclui renderização de conteúdo interrompida, perda de recursos do aplicativo e segurança comprometida.

Criar manualmente uma árvore de renderização (RenderTreeBuilder)

RenderTreeBuilder fornece métodos para manipular componentes e elementos, incluindo a criação manual de componentes no código C#.

Considere o componente a seguir PetDetails, que pode ser renderizado manualmente em outro componente.

PetDetails.razor:

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

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

No componente BuiltContent a seguir, o loop no método CreateComponent gera três componentes PetDetails.

Nos métodos RenderTreeBuilder com um número de sequência, os números de sequência são números de linha do código-fonte. O algoritmo de diferença Blazor depende dos números de sequência correspondentes a linhas de código distintas, não invocações de chamadas distintas. Ao criar um componente com métodos RenderTreeBuilder, codifique os argumentos para números de sequência. O uso de um cálculo ou contador para gerar o número de sequência pode levar a um desempenho ruim. Para obter mais informações, consulte a seção Números de sequência relacionados a números de linha de código e não à ordem de execução.

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

Aviso

Os tipos em Microsoft.AspNetCore.Components.RenderTree permitem o processamento dos resultados de operações de renderização. Estes são detalhes internos da implementação da estrutura Blazor. Esses tipos devem ser considerados instáveis e sujeitos a alterações em versões futuras.

Os números de sequência estão relacionados a números de linha de código e não à ordem de execução

Os arquivos de componente Razor (.razor) são sempre compilados. A execução do código compilado tem uma vantagem potencial sobre a interpretação de código porque a etapa de compilação que produz o código compilado pode ser usada para inserir informações que melhoram o desempenho do aplicativo em runtime.

Um exemplo importante dessas melhorias envolve números de sequência. Os números de sequência indicam para o runtime quais saídas vieram das linhas de código distintas e ordenadas. O runtime usa essas informações para gerar diferenças de árvore eficientes em tempo linear, o que é muito mais rápido do que normalmente é possível para um algoritmo de comparação de árvore geral.

Considere o seguinte componente Razor (.razor) a seguir:

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

Second

A marcação Razor anterior e o conteúdo de texto são compilados em código C# semelhante ao seguinte:

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

builder.AddContent(1, "Second");

Quando o código é executado pela primeira vez e someFlag é true, o construtor recebe a sequência na tabela a seguir.

Sequência Tipo Dados
0 Nó de texto First
1 Nó de texto Segunda

Imagine que someFlag se torna false e a marcação é renderizada novamente. Desta vez, o construtor recebe a sequência na tabela a seguir.

Sequência Tipo Dados
1 Nó de texto Segunda

Quando o runtime executa uma comparação, é visualizado o item em sequência 0 que foi removido, portanto, gera o seguinte script de edição normal com uma única etapa:

  • Remova o primeiro nó de texto.

O problema de gerar números de sequência programaticamente

Em vez disso, imagine que você escreveu a seguinte lógica de construtor de árvore de renderização:

var seq = 0;

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

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

A primeira saída é mostrada na tabela a seguir:

Sequência Tipo Dados
0 Nó de texto First
1 Nó de texto Segunda

Esse resultado é idêntico ao caso anterior, portanto, não existem problemas negativos. someFlag está false na segunda renderização e a saída é vista na tabela a seguir.

Sequência Tipo Dados
0 Nó de texto Segunda

Desta vez, o algoritmo de comparação visualiza que duas alterações ocorreram. O algoritmo gera o seguinte script de edição:

  • Altere o valor do primeiro nó de texto para Second.
  • Remova o segundo nó de texto.

Para gerar os números de sequência que perderam todas as informações úteis sobre onde os branches if/else e loops estavam presentes no código original. Dessa forma, em uma comparação duas vezes maior do que antes.

Este é um exemplo trivial. Em casos mais realistas com estruturas complexas e profundamente aninhadas e especialmente com loops, o custo de desempenho geralmente é maior. Em vez de identificar imediatamente quais blocos ou ramificações de loop foram inseridos ou removidos, o algoritmo de comparação deve se repetir profundamente nas árvores de renderização. Em geral, resulta na criação de scripts de edição mais longos porque o algoritmo de comparação está mal informado sobre como as estruturas antigas e novas se relacionam entre si.

Diretrizes e conclusões

  • O desempenho do aplicativo tem impacto se os números de sequência forem gerados dinamicamente.
  • As informações necessárias não existem para permitir que a estrutura gere números sequenciais automaticamente no runtime, a menos que sejam capturadas em tempo de compilação.
  • Não escreva blocos longos de lógica RenderTreeBuilder implementada manualmente. Prefira arquivos .razor e permita que o compilador lide com os números de sequência. Se não for possível evitar a lógica manual RenderTreeBuilder, divida blocos longos de código em partes menores encapsuladas em chamadas OpenRegion/CloseRegion. Cada região tem seu próprio espaço separado de números de sequência, para que você possa reiniciar a partir de zero (ou qualquer outro número arbitrário) dentro de cada região.
  • Se os números de sequência forem codificados, o algoritmo de comparação exige apenas que os números de sequência aumentem de valor. O valor inicial e as lacunas são irrelevantes. Uma opção legítima é usar o número da linha de código como o número de sequência ou começar do zero e aumentar em um ou centenas (ou qualquer intervalo preferencial).
  • Para loops, os números de sequência devem aumentar em seu código-fonte, não em termos de comportamento de runtime. O fato de que, em runtime, os números se repetem é como o sistema de comparação (diffing) percebe que você está em um loop.
  • Blazor usa números de sequência, enquanto outras estruturas de interface do usuário com diferenciação de árvore não usam. A comparação é muito mais rápida quando os números de sequência são usados e Blazor tem a vantagem de uma etapa de compilação que lida com números de sequência automaticamente para desenvolvedores que criam arquivos .razor.