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 a versão atual, consulte a versão .NET 9 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 a versão atual, consulte a versão .NET 9 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
.