Виртуализация компонентов Razor

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

Пример без виртуализации

Допустим, ваше приложение должно показывать некие сообщения пользователю при входе в приложение. Для этого в приложении создан специальный компонент Message, например, вот такой:

<div class="alert alert-primary" role="alert">
    @Text
</div>

@code {
    [Parameter]
    public string Text { get; set; } = "";
}

и таких сообщений может быть тысячи (вместо сообщений можете представить себе другие данные — списки пользователей, карточки товаров, телефонный справочник и так далее). Создадим новое приложение Blazor Hybrid, добавим в папку Components представленный выше компонент и изменим код Home.razor следующим образом

@page "/"

<h1>Hello, world!</h1>

@foreach (var message in messages)
{
    <Message Text="@message"></Message>
}

@code
{
    List<string> messages = [];

    protected override void OnInitialized()
    {
        for (int i = 0; i < 100000; i++)
        {
            messages.Add($"Сообщение {i}");
        }
    }
}

здесь в методе OnInitialized() мы «загружаем» сырые данные для наших компонентов Razor. В данном случае — это обычные строки. В разметке мы запускаем цикл по списку messages и рендерим наши компоненты сообщений.

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

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

Компонент <Virtualize>

<Virtualize> — это встроенный компонент, который позволяет виртуализировать список однотипных элементов. Применим этот компонент в нашем приложении, изменив код Home.razor следующим образом:

@page "/"

<h1>Hello, world!</h1>


<Virtualize Items="messages" Context="message">
    <Message Text="@message"></Message>
</Virtualize>
    
@code
{
    List<string> messages = [];

    protected override void OnInitialized()
    {
        for (int i = 0; i < 100000; i++)
        {
            messages.Add($"Сообщение {i}");
        }
    }
}

Обратите внимание на то, как используется этот компонент — в качестве дочернего содержимого у него выступает компонент Razor для виртуализации или, другими словами, шаблон элемента виртуализируемого списка. В качестве виртуализируемых элементов у нас выступают строки (список messages) — «сырые» данные. Также у компонента определяется параметр Context, который содержит отдельный элемент списка. Значение для параметра Context — произвольная строка, которая в дальнейшем выступает как переменная с типом равным типу элементов списка. Таким образом, Virtualize как бы заменяет собой обычный цикл, а контекст message будет содержать очередное значение строки из списка messages.

Теперь, если вы запустите приложение, то заметите, что интерфейс рендерится, если не на порядок, то заметно быстрее, чем ранее. В нашем примере мы сразу «загрузили» все элементы списка в память, однако, в реальных приложениях такой подход может быть сопряжен с риском того, что ваше приложение в итоге займет всю оперативную память. Экономичнее загружать большой объем данных по частям и отображать результаты по мере необходимости. Компонент Virtualize позволяет реализовать и такой подход.

Использование делегата поставщика элементов

Делегат поставщика элементов асинхронно загружает данные и выглядит следующим образом:

public delegate ValueTask<ItemsProviderResult<TItem>> ItemsProviderDelegate<TItem>(ItemsProviderRequest request);

Делегаты удобно использовать, например, при запросе к какому-либо внешнему API на постраничное получение данных или для загрузки элементов из базы данных по частям и та далее. В делегате используются следующие типы:

Структура только для чтения ItemsProviderResult — результат выполнения операции. У ItemsProviderResult определены два свойства:

Свойство Тип  Описание
Items IEnumerable<TItem> Последовательность элементов, возвращаемых делегатом
TotalItemCount int Общее количество элементов, которые должны быть возвращены

Структура только для чтения ItemsProviderRequest — запрос на получение данных. Запрос содержит следующие свойства:

Свойство Тип  Описание
CancellationToken Property CancellationToken Токен отмены асинхронной операции
Count int Количество элементов, которые должны быть запрошены
StartIndex int Стартовый индекс сегмента данных, который должен быть запрошен

Перепишем наш пример с сообщениями, используя делегат:

@page "/"

<h1>Hello, world!</h1>


<Virtualize Context="message" ItemsProvider="LoadData">
    <Message Text="@message"></Message>
</Virtualize>

@code
{
    private async ValueTask<ItemsProviderResult<string>> LoadData(
    ItemsProviderRequest request)
    {
        List<string> data = [];
        for (int i = 0; i < 100; i++)
        {
            data.Add($"Сообщение {request.StartIndex+i}");
        }
        return new ItemsProviderResult<string>(data, 100000);
    }
}

В делегате мы запрашиваем данные частями по 100 штук. Нетрудно представить, что на месте обычного цикла в этом делегате может быть запрос к API или базе данных приложения. Делегат указывается в параметре ItemsProvider компонента Message. Можете запустить приложение и убедиться, что оно работает как и прежде, но, при этом, уже не загружает все 100 000 строк в память, в обрабатывает их по частям.

Так как загрузка данных из внешнего источника может происходить достаточно продолжительное время, то в компоненте Virtualize предусмотрено использование плейсхолдера, который будет выводиться вместо элемента пока обрабатывается очередной вызов делегата. Например,

<Virtualize Context="message" ItemsProvider="LoadData">
    <ItemContent>
        <Message Text="@message"></Message>
    </ItemContent>
    <Placeholder>
        <p>Ждем...</p>
    </Placeholder>
</Virtualize>

Здесь мы в качестве плейсхолдера используем обычную строку «Ждем…». Теперь, если при загрузке данных будут происходить задержки, то вместо пустой страницы приложения мы увидим:

Итого

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

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии