Виртуализация компонентов ASP.NET Core Razor
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 9 этой статьи.
В этой статье вы узнаете, как использовать виртуализацию компонентов в приложениях ASP.NET Core Blazor.
Виртуализация
Повышение производительности отрисовки компонентов с помощью Blazor встроенной поддержки виртуализации платформы с компонентом Virtualize<TItem> . Виртуализация — это метод отображения только видимых в данный момент частей пользовательского интерфейса. Например, виртуализация удобна в случае, когда в приложении должен быть отрисован длинный список элементов и в любой конкретный момент времени должно быть видимым только подмножество элементов.
Используйте компонент Virtualize<TItem> в таких случаях:
- при отрисовке набора элементов данных в цикле;
- если большинство элементов не видны из-за настроек прокрутки;
- если отображаемые элементы имеют одинаковый размер.
Когда пользователь прокручивает с произвольной точки в списке элементов компонента Virtualize<TItem>, компонент вычисляет видимые элементы для отображения. Невидимые элементы не отрисовываются.
Без виртуализации обычный список может использовать цикл C# foreach
для отрисовки каждого элемента в списке. В следующем примере :
allFlights
представляет собой коллекцию рейсов самолетов.- Компонент
FlightSummary
отображает сведения о каждом рейсе. - Атрибут директивы
@key
сохраняет связь каждого компонентаFlightSummary
с его отображаемым рейсом элементаFlightId
рейса.
<div style="height:500px;overflow-y:scroll">
@foreach (var flight in allFlights)
{
<FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
}
</div>
Если коллекция содержит тысячи рейсов, отрисовка рейсов занимает много времени и пользователи сталкиваются с заметной задержкой отображения пользовательского интерфейса. Большинство полетов выходят за пределы высоты <div>
элемента, поэтому большинство из них не видно.
Вместо отрисовки сразу всего списка рейсов замените цикл foreach
в предыдущем примере на компонент Virtualize<TItem>:
Укажите
allFlights
как источник фиксированного элемента для Virtualize<TItem>.Items. Компонент Virtualize<TItem> выполняет отрисовку только видимых в данный момент рейсов.Если не универсальная коллекция предоставляет элементы, например коллекцию DataRow, следуйте указаниям в разделе делегата поставщика элементов, чтобы предоставить элементы.
Укажите контекст для каждого рейса с помощью параметра
Context
. В следующем примере элементflight
используется в качестве контекста, который обеспечивает доступ к каждому участнику рейса.
<div style="height:500px;overflow-y:scroll">
<Virtualize Items="allFlights" Context="flight">
<FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
</Virtualize>
</div>
Если контекст не указан с помощью параметра Context
, используйте значение context
в шаблоне содержимого элемента, чтобы получить доступ к членам каждого рейса:
<div style="height:500px;overflow-y:scroll">
<Virtualize Items="allFlights">
<FlightSummary @key="context.FlightId" Details="@context.Summary" />
</Virtualize>
</div>
Компонент Virtualize<TItem>:
- вычисляет количество подлежащих отрисовке элементов на основе высоты контейнера и размера отображаемых элементов;
- пересчитывает и повторно отрисовывает элементы при прокрутке пользователем;
- Извлекает только срез записей из внешнего API, соответствующий текущему видимому региону, включая overscan, когда
ItemsProvider
используется вместоItems
(см . раздел делегата поставщика элементов).
Содержимое элемента для компонента Virtualize<TItem> может включать в себя следующее:
- обычный код HTML и код Razor, как показано в предыдущем примере;
- один или несколько компонентов Razor;
- сочетание компонентов HTML/Razor и Razor.
Делегат поставщика элементов
Если вы не хотите загружать все элементы в память или коллекция не является универсальным интерфейсом ICollection<T>, можно указать метод делегата поставщика элементов для параметра Virtualize<TItem>.ItemsProvider компонента, который асинхронно извлекает запрошенные элементы по запросу. В следующем примере метод LoadEmployees
предоставляет элементы компоненту Virtualize<TItem>.
<Virtualize Context="employee" ItemsProvider="LoadEmployees">
<p>
@employee.FirstName @employee.LastName has the
job title of @employee.JobTitle.
</p>
</Virtualize>
Поставщик элементов получает ItemsProviderRequest, который указывает необходимое количество элементов, начиная с заданного начального индекса. Затем поставщик элементов извлекает запрошенные элементы из базы данных или другой службы и возвращает их в виде ItemsProviderResult<TItem> вместе с количеством всех элементов. Поставщик элементов может извлекать элементы с каждым запросом или кэшировать их, чтобы они были доступны.
Компонент Virtualize<TItem> может принимать только один источник элемента из параметров, поэтому не пытайтесь одновременно использовать поставщик элементов и назначить коллекцию для Items
. Если назначаются оба значения, создается InvalidOperationException, когда параметры компонента задаются во время выполнения.
Следующий пример загружает сотрудников из EmployeeService
(не показан). Обычно totalEmployees
это поле назначается путем вызова метода в той же службе (например, в другом месте), EmployeesService.GetEmployeesCountAsync
например во время инициализации компонента.
private async ValueTask<ItemsProviderResult<Employee>> LoadEmployees(
ItemsProviderRequest request)
{
var numEmployees = Math.Min(request.Count, totalEmployees - request.StartIndex);
var employees = await EmployeesService.GetEmployeesAsync(request.StartIndex,
numEmployees, request.CancellationToken);
return new ItemsProviderResult<Employee>(employees, totalEmployees);
}
В следующем примере коллекция DataRow не является универсальной, поэтому для виртуализации используется делегат поставщика элементов:
<Virtualize Context="row" ItemsProvider="GetRows">
...
</Virtualize>
@code{
...
private ValueTask<ItemsProviderResult<DataRow>> GetRows(ItemsProviderRequest request) =>
new(new ItemsProviderResult<DataRow>(
dataTable.Rows.OfType<DataRow>().Skip(request.StartIndex).Take(request.Count),
dataTable.Rows.Count));
}
Virtualize<TItem>.RefreshDataAsync указывает компоненту на необходимость повторного запроса данных из ItemsProvider. Это полезно в тех случаях, когда внешние данные изменяются. Вызывать метод RefreshDataAsync при использовании Items обычно не требуется.
RefreshDataAsync обновляет данные компонента Virtualize<TItem>, не приводя к повторной отрисовке. Если RefreshDataAsync вызывается из обработчика событий Blazor или метода жизненного цикла компонента, активация отрисовки не требуется, поскольку она автоматически активируется в конце обработчика событий или метода жизненного цикла. Если RefreshDataAsync запускается отдельно от фоновой задачи или события, например в следующем делегате ForecastUpdated
, вызовите метод StateHasChanged, чтобы обновить пользовательский интерфейс в конце фоновой задачи или события:
<Virtualize ... @ref="virtualizeComponent">
...
</Virtualize>
...
private Virtualize<FetchData>? virtualizeComponent;
protected override void OnInitialized()
{
WeatherForecastSource.ForecastUpdated += async () =>
{
await InvokeAsync(async () =>
{
await virtualizeComponent?.RefreshDataAsync();
StateHasChanged();
});
});
}
В предыдущем примере:
- RefreshDataAsync вызывается первым, чтобы получить новые данные для компонента Virtualize<TItem>.
StateHasChanged
вызывается для повторной отрисовки компонента.
Заполнитель
Поскольку запрос элементов из удаленного источника данных может занимать некоторое время, можно отрисовать заполнитель с содержимым элемента.
- Используйте Placeholder (
<Placeholder>...</Placeholder>
) для отображения содержимого до тех пор, пока не будут доступны данные элемента. - Чтобы задать шаблон элемента для списка, используйте Virtualize<TItem>.ItemContent.
<Virtualize Context="employee" ItemsProvider="LoadEmployees">
<ItemContent>
<p>
@employee.FirstName @employee.LastName has the
job title of @employee.JobTitle.
</p>
</ItemContent>
<Placeholder>
<p>
Loading…
</p>
</Placeholder>
</Virtualize>
Пустое содержимое
EmptyContent Используйте параметр для предоставления содержимого при загрузке компонента и Items пуст или ItemsProviderResult<TItem>.TotalItemCount равен нулю.
EmptyContent.razor
:
@page "/empty-content"
<PageTitle>Empty Content</PageTitle>
<h1>Empty Content Example</h1>
<Virtualize Items="stringList">
<ItemContent>
<p>
@context
</p>
</ItemContent>
<EmptyContent>
<p>
There are no strings to display.
</p>
</EmptyContent>
</Virtualize>
@code {
private List<string>? stringList;
protected override void OnInitialized() => stringList ??= new();
}
@page "/empty-content"
<PageTitle>Empty Content</PageTitle>
<h1>Empty Content Example</h1>
<Virtualize Items="stringList">
<ItemContent>
<p>
@context
</p>
</ItemContent>
<EmptyContent>
<p>
There are no strings to display.
</p>
</EmptyContent>
</Virtualize>
@code {
private List<string>? stringList;
protected override void OnInitialized() => stringList ??= new();
}
Измените лямбда-метод, OnInitialized
чтобы просмотреть строки отображения компонента:
protected override void OnInitialized() =>
stringList ??= new() { "Here's a string!", "Here's another string!" };
Размер элемента
Высоту каждого элемента в пикселях можно задать с помощью Virtualize<TItem>.ItemSize (по умолчанию: 50). В следующем примере высота каждого элемента изменяется со стандартного значения 50 пикселей на 25 пикселей:
<Virtualize Context="employee" Items="employees" ItemSize="25">
...
</Virtualize>
Компонент Virtualize<TItem> измеряет размер отрисовки (высота) отдельных элементов после первоначальной отрисовки. Используйте ItemSize, чтобы заранее предоставить точный размер элемента и обеспечить правильную первоначальную производительность отрисовки, а также убедиться в правильности позиции прокрутки для перегрузки страниц. Если по умолчанию ItemSize некоторые элементы будут отображаться вне видимого представления, активируется второй rerender. Чтобы обеспечить правильное расположение элементов прокрутки в виртуализированном списке в браузере, начальная прорисовка должна быть правильной. В противном случае пользователи могут просматривать не те элементы.
Количество элементов в нерабочей области
Virtualize<TItem>.OverscanCount определяет количество дополнительных элементов, отрисовываемых до и после видимой области. Этот параметр позволяет уменьшить частоту отрисовки во время прокрутки. Однако более высокие значения приводят к отображению большего числа элементов на странице (по умолчанию: 3). В следующем примере количество элементов в нерабочей области изменяется с трех элементов (стандартное значение) на четыре:
<Virtualize Context="employee" Items="employees" OverscanCount="4">
...
</Virtualize>
Изменения состояний
При внесении изменений в элементы, отображаемые компонентом Virtualize<TItem> , вызовите StateHasChanged повторное вычисление и повторное выполнение компонента. Дополнительные сведения см. в статье Отрисовка компонентов Razor ASP.NET Core.
Поддержка прокрутки с клавиатуры
Чтобы разрешить пользователям прокручивать виртуализированное содержимое с помощью клавиатуры, убедитесь, что виртуализованные элементы или сам контейнер прокрутки являются фокусируемыми. Если не выполнить этот шаг, прокрутка с клавиатуры не будет работать в браузерах на основе Chromium.
Например, атрибут tabindex
можно использовать в контейнере прокрутки:
<div style="height:500px; overflow-y:scroll" tabindex="-1">
<Virtualize Items="allFlights">
<div class="flight-info">...</div>
</Virtualize>
</div>
Дополнительные сведения о значении tabindex
-1
, 0
или других значениях см. в разделе tabindex
(документация по MDN).
Расширенные стили и обнаружение прокрутки
Компонент Virtualize<TItem> предназначен только для поддержки конкретных механизмов макетов элементов. Чтобы можно было понять, какие макеты элементов работают правильно, далее объясняется, как Virtualize
определяет, какие элементы должны быть видимы для отображения в правильном месте.
Если исходный код выглядит следующим образом:
<div style="height:500px; overflow-y:scroll" tabindex="-1">
<Virtualize Items="allFlights" ItemSize="100">
<div class="flight-info">Flight @context.Id</div>
</Virtualize>
</div>
В среде выполнения компонент Virtualize<TItem> отрисовывает структуру DOM следующим образом:
<div style="height:500px; overflow-y:scroll" tabindex="-1">
<div style="height:1100px"></div>
<div class="flight-info">Flight 12</div>
<div class="flight-info">Flight 13</div>
<div class="flight-info">Flight 14</div>
<div class="flight-info">Flight 15</div>
<div class="flight-info">Flight 16</div>
<div style="height:3400px"></div>
</div>
Фактическое число отображаемых строк и размер разделителей зависят от стиля и размера коллекции Items
. Однако обратите внимание, что перед содержимым и после него есть элементы разделителей div
. Они служат двум целям:
- Чтобы обеспечить смещение до и после содержимого, в результате чего видимые элементы будут отображаться в правильном расположении в диапазоне прокрутки, а сам диапазон прокрутки будет представлять общий размер всего содержимого.
- Чтобы определить, когда пользователь выполняет прокрутку за пределами текущего видимого диапазона, то есть должно быть отрисовано другое содержимое.
Примечание.
Чтобы узнать, как управлять тегом HTML-элемента разделителя, см. раздел Управление именем тега элемента разделителя далее в этой статье.
Элементы разделителя внутренне используют наблюдатель пересечения, чтобы знать, когда они становятся видимыми. Virtualize
зависит от получения этих событий.
Virtualize
работает в следующих условиях:
Все отображаемые элементы содержимого, включая заполнитель, имеют одинаковую высоту. Это позволяет вычислить содержимое, соответствующее заданной позиции прокрутки, без предварительной выборки каждого элемента данных и отрисовки данных в элемент DOM.
Пробелы и строки содержимого отрисовываются в одном вертикальном стеке с каждым элементом, заполняя всю горизонтальную ширину. В типичных вариантах использования работает с
div
элементамиVirtualize
. Если вы используете CSS для создания расширенного макета, учитывайте следующие требования:- Для стилизации контейнера прокрутки требуется
display
любой из следующих значений:block
(значение по умолчанию дляdiv
).table-row-group
(значение по умолчанию дляtbody
).flex
с параметромflex-direction
со значениемcolumn
. Убедитесь, что непосредственные дочерние элементы компонента Virtualize<TItem> не сжимаются в соответствии с правилами гибкого подхода. Например, добавьте.mycontainer > div { flex-shrink: 0 }
.
- Для стилизации строк содержимого требуется
display
одно из следующих значений:block
(значение по умолчанию дляdiv
).table-row
(значение по умолчанию дляtr
).
- Не используйте CSS, чтобы изменить макет с элементами разделителей. Элементы пробелов имеют
display
значениеblock
, за исключением того, если родительский элемент является группой строк таблицы, в этом случае они по умолчаниюtable-row
. Не пытайтесь повлиять на ширину или высоту элемента разделителя, включая настройку границы или псевдо-элементовcontent
.
- Для стилизации контейнера прокрутки требуется
Любой подход, который мешает элементам разделителей и содержимого отрисовываться в виде одного вертикального стека или приводит к различию в высоте элементов, нарушает функционирование компонента Virtualize<TItem>.
Виртуализация на корневом уровне
Компонент Virtualize<TItem> поддерживает использование самого документа в качестве корня прокрутки как альтернативу использованию другого элемента с overflow-y: scroll
. В следующем примере для элементов <html>
или <body>
стиль настраивается в компоненте с помощью overflow-y: scroll
:
<HeadContent>
<style>
html, body { overflow-y: scroll }
</style>
</HeadContent>
Компонент Virtualize<TItem> поддерживает использование самого документа в качестве корня прокрутки как альтернативу использованию другого элемента с overflow-y: scroll
. При использовании документа в качестве корня прокрутки не настраивайте стиль элементов <html>
или <body>
с помощью overflow-y: scroll
, так как это приводит к тому, что наблюдатель пересечения обрабатывает всю прокручиваемую высоту страницы как видимую область, а не только окно просмотра окна.
Эту проблему можно воспроизвести, создав большой виртуализированный список (например, 100 000 элементов) и попытавшись использовать документ в качестве корня прокрутки с параметром html { overflow-y: scroll }
на странице стилей CSS. Хотя иногда это может сработать, браузер пытается отрисовать все 100 000 элементов по крайней мере один раз в начале отрисовки, что может привести к блокировке вкладки браузера.
Чтобы обойти эту проблему до выпуска .NET 7, не используйте <html>
/<body>
overflow-y: scroll
альтернативный подход. В следующем примере высота элемента <html>
имеет значение чуть более 100 % высоты окна просмотра:
<HeadContent>
<style>
html { min-height: calc(100vh + 0.3px) }
</style>
</HeadContent>
Компонент Virtualize<TItem> поддерживает использование самого документа в качестве корня прокрутки как альтернативу использованию другого элемента с overflow-y: scroll
. При использовании документа в качестве корня прокрутки избегайте стилизации <html>
элементов или <body>
элементов overflow-y: scroll
, так как это приводит к тому, что полная прокрутка высоты страницы будет рассматриваться как видимая область, а не только окно просмотра.
Эту проблему можно воспроизвести, создав большой виртуализированный список (например, 100 000 элементов) и попытавшись использовать документ в качестве корня прокрутки с параметром html { overflow-y: scroll }
на странице стилей CSS. Хотя иногда это может сработать, браузер пытается отрисовать все 100 000 элементов по крайней мере один раз в начале отрисовки, что может привести к блокировке вкладки браузера.
Чтобы обойти эту проблему до выпуска .NET 7, не используйте <html>
/<body>
overflow-y: scroll
альтернативный подход. В следующем примере высота элемента <html>
имеет значение чуть более 100 % высоты окна просмотра:
<style>
html { min-height: calc(100vh + 0.3px) }
</style>
Управление именем тега элемента разделителя
Если компонент Virtualize<TItem> помещается в элемент, которому требуется определенное имя дочернего тега, SpacerElement разрешает получить или задать имя тега разделителя в виртуализации. Значение по умолчанию — div
. В следующем примере компонент Virtualize<TItem> отрисовывается внутри элемента тела таблицы (tbody
), поэтому соответствующий дочерний элемент для строки таблицы (tr
) задается в качестве разделителя.
VirtualizedTable.razor
:
@page "/virtualized-table"
<PageTitle>Virtualized Table</PageTitle>
<HeadContent>
<style>
html, body {
overflow-y: scroll
}
</style>
</HeadContent>
<h1>Virtualized Table Example</h1>
<table id="virtualized-table">
<thead style="position: sticky; top: 0; background-color: silver">
<tr>
<th>Item</th>
<th>Another column</th>
</tr>
</thead>
<tbody>
<Virtualize Items="fixedItems" ItemSize="30" SpacerElement="tr">
<tr @key="context" style="height: 30px;" id="row-@context">
<td>Item @context</td>
<td>Another value</td>
</tr>
</Virtualize>
</tbody>
</table>
@code {
private List<int> fixedItems = Enumerable.Range(0, 1000).ToList();
}
В предыдущем примере корень документа используется в качестве контейнера прокрутки, поэтому для элементов html
и body
стиль задается с помощью overflow-y: scroll
. Дополнительные сведения см. на следующих ресурсах:
ASP.NET Core