Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Примечание.
Это не последняя версия этой статьи. Текущий выпуск можно найти в версии этой статьи о .NET 10.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Для получения дополнительной информации см. Политику поддержки .NET и .NET Core. Текущий выпуск можно найти в версии этой статьи о .NET 10.
В этой статье объясняется, как использовать виртуализацию компонентов в приложениях ASP.NET Core Blazor.
Виртуализация
Улучшите восприятие производительности отрисовки компонентов, используя встроенную поддержку виртуализации платформы Blazor и компонента Virtualize<TItem>. Виртуализация — это метод отображения только видимых в данный момент частей пользовательского интерфейса. Например, виртуализация удобна в случае, когда в приложении должен быть отрисован длинный список элементов и в любой конкретный момент времени должно быть видимым только подмножество элементов.
Используйте компонент Virtualize<TItem> в таких случаях:
- Отрисовка набора элементов данных в цикле.
- Большинство элементов не видны из-за прокрутки.
Когда пользователь прокручивает к произвольной точке в списке элементов компонента Virtualize<TItem>, компонент определяет, какие видимые элементы следует отобразить. Невидимые элементы не отрисовываются.
Без виртуализации обычный список может использовать цикл C# foreach для отрисовки каждого элемента в списке. В следующем примере :
-
allFlightsпредставляет собой коллекцию рейсов самолетов. - Компонент
FlightSummaryотображает сведения о каждом рейсе. - Атрибут директивы
сохраняет связь каждого компонента с его отображаемым рейсом, управляемым элементом .
<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 ??= [];
}
@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 ??= [];
}
Измените лямбда-метод OnInitialized, чтобы увидеть строки отображения компонента.
protected override void OnInitialized() =>
stringList ??= [ "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 до появления каких-либо измерений).
Высоту каждого элемента в пикселях можно задать с помощью Virtualize<TItem>.ItemSize (по умолчанию: 50). В следующем примере высота каждого элемента изменяется со стандартного значения 50 пикселей на 25 пикселей:
<Virtualize Context="employee" Items="employees" ItemSize="25">
...
</Virtualize>
Компонент Virtualize<TItem> измеряет размер отрисовки (высота) отдельных элементов после первоначальной отрисовки. Используйте ItemSize, чтобы заранее предоставить точный размер элемента и обеспечить правильную первоначальную производительность отрисовки, а также убедиться в правильности позиции прокрутки для перегрузки страниц. Если значение по умолчанию ItemSize, некоторые элементы будут отображаться за пределами видимой области, активируется повторный рендеринг. Чтобы обеспечить правильное расположение элементов прокрутки в виртуализированном списке в браузере, начальная прорисовка должна быть правильной. В противном случае пользователи могут просматривать не те элементы.
Количество в области оверскана
Virtualize<TItem>.OverscanCount определяет количество дополнительных элементов, отрисовываемых до и после видимой области. Этот параметр позволяет уменьшить частоту отрисовки во время прокрутки. Однако более высокие значения приводят к тому, что на странице отображается больше элементов (по умолчанию: 15). В следующем примере количество оверскана изменяется с 15 элементов по умолчанию на 17 элементов.
<Virtualize Context="employee" Items="employees" OverscanCount="17">
...
</Virtualize>
Virtualize<TItem>.OverscanCount определяет количество дополнительных элементов, отрисовываемых до и после видимой области. Этот параметр позволяет уменьшить частоту отрисовки во время прокрутки. Однако более высокие значения приводят к отображению большего числа элементов на странице (по умолчанию: 3). В следующем примере количество элементов в переизбыточном сканировании изменяется со стандартного значения три элемента на четыре элемента.
<Virtualize Context="employee" Items="employees" OverscanCount="4">
...
</Virtualize>
Управление положением прокрутки области просмотра при динамическом добавлении элементов
Назначьте параметру VirtualizeAnchorMode значение AnchorMode , чтобы управлять поведением окна просмотра в краях списка при динамическом добавлении элементов:
-
None: Без закрепления по краям. Окно просмотра остается в текущей позиции прокрутки независимо от изменений элемента. -
Beginning: закрепляет окно просмотра в начале списка. Когда пользователь находится почти в начале списка и в его начало добавляются новые элементы, область просмотра остается вверху, показывая самые новые элементы. Например, такое поведение закрепления полезно для интерфейса ленты новостей. -
End: закрепляет окно просмотра в конце списка. Когда пользователь прокрутил список почти до конца и в его конец поступают новые элементы, область просмотра автоматически прокручивается, чтобы показать их. Если пользователь прокрутил содержимое вверх от нижней части, автопрокрутка отключается, пока он не вернётся вниз. Например, это поведение закрепления полезно для чата или ведения журнала пользователей.
Следующий пример фиксирует область просмотра в начале виртуализированного списка рейсов:
<div style="height:500px;overflow-y:scroll">
<Virtualize Items="allFlights" Context="flight" AnchorMode="Beginning">
<FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
</Virtualize>
</div>
Режимы можно объединить. Например, назначение Beginning | End закрепляет оба края. Объединение None с другими режимами поддерживается, но не изменяет объединенное значение.
Virtualize.ItemComparer возвращает или задает компаратор, используемый для определения того, были ли элементы добавлены в начало или в конец при использовании элементов, типизированных классами, с ItemsProvider (дополнительные сведения см. в разделе Делегат поставщика элементов).
Компаратор определяет, изменился ли первый загруженный элемент между вызовами поставщика данных; это указывает на то, что элементы были вставлены в начало списка. Для записей поведение сравнения по значению у сравнивателя по умолчанию (EqualityComparer<T>.Default) работает автоматически. Для назначения в памяти Items средство сравнения не требуется, ItemComparer так как компонент может автоматически обнаруживать предустановки. В случаях, когда непримитивные объекты виртуализированы и платформа не может определить, является ли элемент предустановленным или добавленным, назначьте компоненту IEqualityComparer<T>Virtualize :
<Virtualize ItemsProvider="LoadFlights" AnchorMode="Beginning"
ItemComparer="itemComparer">
...
</Virtualize>
@code {
private static readonly IEqualityComparer<Flight> itemComparer =
EqualityComparer<Flight>.Create((a, b) =>
a.Index == b.Index, item => item.Index);
private async ValueTask<ItemsProviderResult<Flight>> LoadFlights(
ItemsProviderRequest request)
{
...
}
}
Изменения состояний
При изменении элементов, отображаемых компонентом Virtualize<TItem>, вызовите StateHasChanged, чтобы запланировать повторную оценку и перерисовку компонента. Дополнительные сведения см. в разделе ASP.NET Core Razor отрисовка компонентов.
Поддержка прокрутки с клавиатуры
Чтобы разрешить пользователям прокручивать виртуализированное содержимое с помощью клавиатуры, убедитесь, что виртуализованные элементы или сам контейнер прокрутки являются фокусируемыми. Если не выполнить этот шаг, прокрутка с клавиатуры не будет работать в браузерах на основе 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.
Расширенные стили и обнаружение прокрутки
Компонент 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.
Разделители и строки содержимого отрисовываются в одной вертикальной колонке, и каждый элемент заполняет всю горизонтальную ширину. В типичных вариантах использования
Virtualizeработает с элементамиdiv. Если вы используете CSS для создания расширенного макета, учитывайте следующие требования:- Для стилизации контейнера прокрутки требуется, чтобы
displayимел одно из следующих значений:-
block(значение по умолчанию дляdiv). -
table-row-group(значение по умолчанию дляtbody). -
flexс параметромflex-directionустановленным вcolumn. Убедитесь, что непосредственные дочерние элементы компонента Virtualize<TItem> не сжимаются в соответствии с правилами flex. Например, добавьте.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
ASP.NET Core