Содержание
Виртуализация — это метод ограничения рендеринга пользовательского интерфейса только теми частями, которые видны в данный момент времени. Довольно часто, в приложениях обрабатываются большие массивы данных, которые могут отображаться в виде каких-либо компонентов 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
— запрос на получение данных. Запрос содержит следующие свойства:
Свойство | Тип | Описание |
Cancellation |
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, а также позволяет организовать загрузку данных из источника сегментами, используя специальный делегат.