Бөлісу құралы:


Расширенные сценарии ASP.NET Core Blazor (построение дерева визуализации)

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

В этой статье описывается расширенный сценарий создания деревьев визуализации Blazor вручную с помощью RenderTreeBuilder.

Предупреждение

Использование RenderTreeBuilder для создания компонентов является сложным сценарием. Неправильно сформированный компонент (например, незакрытый тег разметки) может привести к неопределенному поведению. Неопределенное поведение подразумевает неработоспособную отрисовку содержимого, утрату функций приложения и скомпрометированную безопасность.

Создание дерева визуализации вручную (RenderTreeBuilder)

RenderTreeBuilder предоставляет методы для управления компонентами и элементами, включая создание компонентов вручную в коде C#.

Рассмотрим следующий компонент PetDetails, который можно вручную отрисовать в другом компоненте.

PetDetails.razor:

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

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

В следующем компоненте BuiltContent цикл в методе CreateComponent создает три компонента PetDetails.

В методах RenderTreeBuilder с порядковым номером порядковые номера представляют собой номера строк исходного кода. Алгоритм сравнения Blazor использует порядковые номера, соответствующие отдельным строкам кода, а не отдельным вызовам. При создании компонента с помощью методов RenderTreeBuilder жестко задавайте аргументы для порядковых номеров. Использование вычисления или счетчика для формирования порядкового номера может привести к снижению производительности. Дополнительные сведения см. в разделе Порядковые номера относятся к номерам строк кода, а не порядку выполнения.

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

Предупреждение

Типы в Microsoft.AspNetCore.Components.RenderTree позволяют обрабатывать результаты операций отрисовки. Это внутренние сведения реализации платформы Blazor. Эти типы следует считать нестабильными. Они могут измениться в будущих выпусках.

Порядковые номера относятся к номерам строк кода, а не порядку выполнения

Файлы компонентов Razor (.razor) всегда компилируются. Выполнение скомпилированного кода потенциально лучше, чем интерпретация кода, поскольку шаг компиляции, на котором создается скомпилированный код, можно использовать для вставки сведений, повышающих производительность приложения во время выполнения.

Ключевой пример этих усовершенствований связан с порядковыми номерами. Порядковые номера указывают среде выполнения, какие выходные данные поступили из отдельных и упорядоченных строк кода. Среда выполнения использует эти сведения для эффективного сравнения деревьев в линейном времени, а это гораздо быстрее, чем при использовании общего алгоритма сравнения деревьев.

Рассмотрим следующий файл компонента Razor (.razor):

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

Second

Предыдущая разметка Razor и текстовое содержимое компилируются в код C#, аналогичный следующему:

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

builder.AddContent(1, "Second");

Если код выполняется в первый раз и someFlag имеет значение true, построитель получит последовательность, указанную в следующей таблице.

Sequence Тип Data
0 Текстовый узел First
1 Текстовый узел Second

Представьте, что someFlag становится false, и разметка снова отрисовывается. На этот раз построитель получит последовательность в приведенной ниже таблице.

Sequence Тип Data
1 Текстовый узел Second

Когда среда выполнения выполняет сравнение, она видит, что элемент в последовательности 0 был удален, поэтому за один шаг создает следующий тривиальный скрипт изменения:

  • Удаление первого текстового узла.

Проблема с созданием порядковых номеров программным способом

Представьте себе, что вместо этого вы написали следующую логику отрисовки построителя деревьев:

var seq = 0;

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

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

Первые выходные данные отражаются в следующей таблице.

Sequence Тип Data
0 Текстовый узел First
1 Текстовый узел Second

Этот результат идентичен предыдущему случаю, поэтому проблемы не возникают. someFlag равно false на второй отрисовке, а выходные данные — в следующей таблице.

Sequence Тип Data
0 Текстовый узел Second

В этот раз алгоритм сравнения видит, что произошло два изменения. Алгоритм создает следующий скрипт изменения:

  • Изменение значения первого текстового узла на Second.
  • Удаление второго текстового узла.

При формировании порядковых номеров теряются все полезные сведения о том, где в исходном коде находятся циклы и ветви if/else. Это приводит к тому, что сравнение становится в два раза больше.

Это упрощенный пример. В более реалистичных случаях со сложными и глубокими вложенными структурами, особенно с циклами, затраты на производительность обычно выше. Вместо того чтобы сразу определять, какие блоки или ветви цикла были вставлены или удалены, алгоритм сравнения должен выполнять рекурсивный глубокий переход к деревьям отрисовки. В результате, как правило, создаются более длинные скрипты изменений, поскольку алгоритм сравнения не владеет всей информацией о том, как старые и новые структуры связаны друг с другом.

Рекомендации и выводы

  • Производительность приложения снижается, если порядковые номера создаются динамически.
  • Необходимые сведения не существуют для того, чтобы платформа автоматически создавала порядковые номера во время выполнения, если данные не записываются во время компиляции.
  • Не записывайте длинные блоки логики RenderTreeBuilder, реализуемой вручную. Используйте файлы .razor и дайте компилятору обрабатывать порядковые номера. Если не удается избежать реализуемой вручную логики RenderTreeBuilder, разделите длинные блоки кода на более мелкие части, заключенные в вызовы OpenRegion/CloseRegion. Каждый регион имеет собственное отдельное пространство порядковых номеров, поэтому вы можете снова начинать с нуля (или любого другого произвольного числа) внутри каждого региона.
  • Если порядковые номера задаются жестко, то для алгоритма сравнения требуется только, чтобы порядковые номера увеличивались. Начальное значение и пропуски несущественны. Кроме того, можно использовать номер строки кода в качестве порядкового номера или начать с нуля и увеличивать значение на единицы или сотни (или выбрать любой другой интервал).
  • Для циклов порядковые номера должны увеличиваться в исходном коде, а не с точки зрения поведения среды выполнения. Тот факт, что во время выполнения числа повторяются, как система диффинга понимает, что вы находитесь в цикле.
  • Blazor использует порядковые номера, а другие платформы пользовательского интерфейса для сравнения деревьев не используют их. Сравнение выполняется гораздо быстрее, если используются порядковые номера, и Blazor имеет преимущество этапа компиляции, который автоматически обрабатывает порядковые номера для разработчиков, создающих файлы .razor.