Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Замечание
Это не последняя версия этой статьи. В текущей версии см. версию .NET 10 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Для получения дополнительной информации см. Политику поддержки .NET и .NET Core. В текущей версии см. версию .NET 10 этой статьи.
Оптимизируйте скорость отрисовки, чтобы свести к минимуму связанную с отрисовкой рабочую нагрузку и повысить скорость реагирования пользовательского интерфейса, что может привести к увеличению скорости отрисовки пользовательского интерфейса в десять раз или даже больше.
Избегайте ненужного рендеринга поддеревьев компонентов
Вы можете попытаться исключить основную часть затрат на рендеринг родительского компонента, пропуская рендеринг поддеревьев дочернего компонента, когда происходит событие. Вас должно волновать только пропуск отрисовки тех поддеревьев, которые особенно ресурсоемкие и вызывают задержки в пользовательском интерфейсе.
В среде выполнения все компоненты занимают определенное место в иерархии. Корневой компонент (первый загруженный компонент) содержит дочерние компоненты. В свою очередь, дочерние элементы корня имеют собственные дочерние компоненты, и так далее. При возникновении события, например, при нажатии кнопки пользователем, выбор компонентов для повторного отрисовывания осуществляется по следующему алгоритму.
- Возникшее событие отправляется в тот компонент, который создал обработчик такого события. После выполнения обработчика событий соответствующий компонент повторно отрисовывается.
- При повторной отрисовке компонента новая копия значений параметров передаётся каждому из его дочерних компонентов.
- После получения нового набора значений параметров Blazor решает, следует ли перерендерить компонент. Компоненты перерисовываются, если
ShouldRenderвозвращаетtrue, что является поведением по умолчанию, если это не переопределено, а значения параметров могут измениться, например, если они являются изменяемыми объектами.
Далее два последних шага рекурсивно повторяются для всех компонентов вниз по иерархии. Во многих случаях происходит перерисовка всего поддерева. События, направленные на компоненты высокого уровня, могут вызвать дорогостоящую повторную отрисовку, так как каждый компонент, находящийся ниже в иерархии, также должен быть переотрисован.
Избежать рекурсивной повторной отрисовки всех компонентов в поддереве можно одним из следующих способов.
- Убедитесь, что дочерние параметры компонента имеют определенные неизменяемые типы†, например
string, ,intboolиDateTime. Встроенная логика обнаружения изменений автоматически пропускает rerendering, если неизменяемые значения параметров не изменились. Если вы рендерите дочерний компонент с помощью<Customer CustomerId="item.CustomerId" />, гдеCustomerId— это компонент типаint, то компонентCustomerне будет перерисовываться, если толькоitem.CustomerIdне изменится. - Переопределить
ShouldRender, чтобы вернутьfalse:- Если параметры являются непримитивными типами или неподдерживаемыми неизменяемыми типами†, например сложными настраиваемыми типами моделей или значениями RenderFragment, и значения параметров не изменились,
- При создании компонента, который предназначен только для пользовательского интерфейса и не изменяется после первоначальной отрисовки, независимо от изменения значения параметра.
† Для получения дополнительных сведений см. логику обнаружения изменений в Blazorсправочном источнике (ChangeDetection.cs).
Замечание
Ссылки в документации на исходный код .NET обычно загружают ветку репозитория по умолчанию, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для конкретного релиза, используйте раскрывающийся список Переключение ветвей или тегов. Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).
В следующем примере средства для поиска авиарейсов используются закрытые поля для отслеживания необходимой информации для обнаружения изменений. Идентификаторы предыдущего прибывающего (prevInboundFlightId) и отбывающего (prevOutboundFlightId) рейсов используются для отслеживания информации для следующего возможного обновления компонента. Если хотя бы один идентификатор рейса изменится при установке параметров компонента в OnParametersSet, происходит повторная отрисовка компонента, поскольку параметр shouldRender имеет значение true. Если после проверки идентификаторов рейса параметр shouldRender имеет значение false, ресурсоемкая повторная отрисовка не производится.
@code {
private int prevInboundFlightId = 0;
private int prevOutboundFlightId = 0;
private bool shouldRender;
[Parameter]
public FlightInfo? InboundFlight { get; set; }
[Parameter]
public FlightInfo? OutboundFlight { get; set; }
protected override void OnParametersSet()
{
shouldRender = InboundFlight?.FlightId != prevInboundFlightId
|| OutboundFlight?.FlightId != prevOutboundFlightId;
prevInboundFlightId = InboundFlight?.FlightId ?? 0;
prevOutboundFlightId = OutboundFlight?.FlightId ?? 0;
}
protected override bool ShouldRender() => shouldRender;
}
Обработчик событий также может присваивать параметру shouldRender значение true. Для большинства компонентов определение необходимости в повторной отрисовке на уровне отдельных обработчиков событий, как правило, не требуется.
Дополнительные сведения см. в следующих ресурсах:
- Жизненный цикл компонента Razor ASP.NET Core
- ShouldRender
-
ASP.NET компонента Core
Виртуализация
Если в некотором цикле выполняется отрисовка значительной части пользовательского интерфейса, например списка или сетки с тысячами записей, само по себе количество операций может приводить к задержке в отрисовке пользовательского интерфейса. Учитывая, что пользователь одновременно без прокрутки видит лишь небольшое количество таких элементов, зачастую излишне тратить время на отрисовку тех элементов, которые пока не видны.
В Blazor предоставляется компонент Virtualize<TItem>, который создает вид и прокрутку для большого списка произвольного размера, отрисовывая только те элементы, которые находятся в текущем окне просмотра прокрутки. Например, компонент может отрисовывать список из 100 000 записей, тратя при этом ресурсы отрисовки только на те 20 элементов, которые видимы в конкретный момент времени.
Дополнительные сведения см. в статье Виртуализация компонентов Razor ASP.NET Core.
Создание простых и оптимизированных компонентов
Большинство компонентов Razor не нуждаются в агрессивной оптимизации, потому что они не переиспользуются в пользовательском интерфейсе и не часто перерисовываются. Например, компоненты, поддерживающие маршрутизацию с директивой @page, и компоненты, используемые для рендеринга элементов пользовательского интерфейса высокого уровня (такие как диалоговые окна или формы), как правило, появляются только по одному и рендерятся только в ответ на жест пользователя. Такие компоненты, как правило, не создают высокой нагрузки на подсистему отрисовки, что позволяет свободно использовать любые сочетания функций платформы, не беспокоясь о производительности при отрисовке.
Тем не менее в некоторых распространенных сценариях компоненты многократно повторяются, что часто приводит к ухудшению производительности пользовательского интерфейса.
- Большие вложенные формы с сотнями отдельных элементов, такими как поля ввода или метки.
- Сетки с сотнями строк или тысячами ячеек.
- Диаграммы рассеяния с миллионами точек данных.
При моделировании каждого элемента, ячейки или точки данных в виде отдельного экземпляра компонента их количество зачастую столь велико, что затраты ресурсов на их отрисовку становятся критичными. В этом разделе собраны рекомендации по упрощению систем с такими компонентами, которые позволят сохранить скорость работы и реагирования пользовательского интерфейса.
Не создавайте тысячи экземпляров компонентов
Каждый компонент является обособленным объектом, который можно отрисовывать отдельно, независимо от его родительских и дочерних элементов. Выбирая способ структурирования пользовательского интерфейса в иерархию компонентов, вы управляете степенью детализации отрисовки пользовательского интерфейса. Это может привести как к хорошей, так и к плохой производительности.
Разделяя пользовательский интерфейс на отдельные компоненты, вы можете выполнять повторную отрисовку небольших частей интерфейса при возникновении событий. В таблице с множеством строк, в каждой из которых есть кнопка, можно обновлять только одну строку с помощью дочернего компонента вместо всей страницы или таблицы. Однако каждый компонент требует дополнительной памяти и нагрузки на ЦП, чтобы справляться со своим состоянием и циклом визуализации.
Инженеры группы ASP.NET Core провели тест, в котором в приложении Blazor WebAssembly затраты на отрисовку каждого экземпляра компонента составили около 0,06 мс. В тестовом приложении отрисовывался простой компонент, который принимал три параметра. На внутреннем уровне издержки связаны в первую очередь с извлечением из словарей состояния для каждого компонента, а также с передачей и получением параметров. Путем умножения, можно видеть, что добавление 2.000 дополнительных экземпляров компонентов увеличит время отрисовки на 0,12 секунды, и пользовательский интерфейс будет казаться пользователю медленным.
Вы можете сделать компоненты более лёгкими, чтобы можно было иметь их больше. Тем не менее часто лучшим подходом будет уменьшение количества отрисовываемых компонентов. В следующих разделах мы опишем оба возможных подхода.
Дополнительные сведения об управлении памятью см. в статье "Управление памятью" в развернутых ASP.NET основных приложениях на стороне Blazor сервера.
Встраивание дочерних компонентов в родительские: Рассмотрим следующий фрагмент родительского компонента, который встроит дочерние компоненты в цикле:
<div class="chat">
@foreach (var message in messages)
{
<ChatMessageDisplay Message="message" />
}
</div>
ChatMessageDisplay.razor:
<div class="chat-message">
<span class="author">@Message.Author</span>
<span class="text">@Message.Text</span>
</div>
@code {
[Parameter]
public ChatMessage? Message { get; set; }
}
Представленный выше пример хорошо работает, пока не придется отобразить одновременно несколько тысяч сообщений. Чтобы отобразить тысячи сообщений одновременно, рассмотрите возможность не выделять отдельный компонент ChatMessageDisplay. Вместо этого следует встроить дочерний компонент в родительский. Описываемый ниже подход позволяет избежать затрат времени на отрисовку множества дочерних компонентов, однако при этом утрачивается возможность повторно отрисовывать разметку независимо для каждого дочернего компонента.
<div class="chat">
@foreach (var message in messages)
{
<div class="chat-message">
<span class="author">@message.Author</span>
<span class="text">@message.Text</span>
</div>
}
</div>
Определите RenderFragments для повторного использования в коде: вы можете выделять дочерние компоненты только для повторного использования логики отрисовки. В этом случае можно создать повторно используемую логику отрисовки без реализации дополнительных компонентов. В блоке @code любого компонента определите фрагмент RenderFragment. Выполняйте рендеринг этого фрагмента из любого места столько раз, сколько необходимо.
@RenderWelcomeInfo
<p>Render the welcome content a second time:</p>
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!</p>;
}
Чтобы сделать RenderTreeBuilder код повторно используемым для нескольких компонентов, объявите RenderFragmentpublic и static:
public static RenderFragment SayHello = @<h1>Hello!</h1>;
В приведенном выше примере SayHello можно вызвать из несвязанного компонента. Такая методика удобна для создания библиотек повторно используемых фрагментов кода разметки, которые позволяют избавиться от затрат ресурсов на отрисовку каждого компонента.
Делегаты RenderFragment могут принимать параметры. Компонент message передает сообщение делегату RenderFragment.
<div class="chat">
@foreach (var message in messages)
{
@ChatMessageDisplay(message)
}
</div>
@code {
private RenderFragment<ChatMessage> ChatMessageDisplay = message =>
@<div class="chat-message">
<span class="author">@message.Author</span>
<span class="text">@message.Text</span>
</div>;
}
В описываемом выше подходе повторно используется логика отрисовки без дополнительных затрат ресурсов на компоненты. Однако данный подход не позволяет обновлять поддерево пользовательского интерфейса независимо, а также не имеет возможности пропускать его отрисовку, когда отрисовывается родительский элемент, поскольку отсутствует граница компонента. Назначение делегату RenderFragment поддерживается только в файлах компонентов Razor (.razor).
Для нестатического поля, метода или свойства, на которые не может ссылаться инициализатор поля, например TitleTemplate в следующем примере, используйте свойство вместо поля для RenderFragment:
protected RenderFragment DisplayTitle =>
@<div>
@TitleTemplate
</div>;
Не принимайте слишком много параметров
Если компонент повторяется очень часто, например сотни или тысячи раз, необходимо помнить о затратах ресурсов на передачу и получение каждого его параметра.
Маловероятно, что слишком большое количество параметров сильно ограничивает производительность, но это может быть фактором.
TableCell Для компонента, который отрисовывает 4000 раз в сетке, каждый параметр, переданный компоненту, добавляет около 15 мс к общей стоимости отрисовки. Передача десяти параметров требует около 150 мс и приводит к задержке отрисовки пользовательского интерфейса.
Чтобы уменьшить связанную с передачей параметров нагрузку, объедините несколько параметров в пользовательском классе. Например, компонент ячейки таблицы может принимать общий объект. В следующем примере значение Data будет разным для каждой ячейки, тогда как Options является общим для всех ее экземпляров.
@typeparam TItem
...
@code {
[Parameter]
public TItem? Data { get; set; }
[Parameter]
public GridOptions? Options { get; set; }
}
Однако помните, что объединение примитивных параметров в класс не всегда является преимуществом. Хотя он может уменьшить число параметров, он также влияет на поведение обнаружения изменений и отрисовки. Передача непримитивных параметров всегда запускает повторную отрисовку, так как Blazor не может знать, могут ли произвольные объекты быть изменены изнутри, тогда как передача примитивных параметров вызывает повторную отрисовку, только если их значения действительно изменились.
Кроме того, рассмотрим, что улучшением может быть отказ от использования компонента ячейки таблицы, как показано в предыдущем примере, и вместо этого встроить его логику в родительский компонент.
Замечание
Чтобы определить подход к повышению производительности, который дает наилучшие результаты среди множества доступных, как правило, требуется тестирование.
Дополнительные сведения о параметрах универсального типа (@typeparam) см. в следующих ресурсах.
- Справочник по синтаксису Razor для ASP.NET Core
- Компоненты Razor ASP.NET Core
- Шаблонные компоненты ASP.NET Core Blazor
Сделайте все каскадные параметры фиксированными
Компонент CascadingValue принимает необязательный параметр IsFixed.
- Если параметр
IsFixedимеет значениеfalse(вариант по умолчанию), каждый получатель каскадного значения настраивает подписку для получения уведомлений об изменениях. Каждый[CascadingParameter]обойдется значительно дороже обычного[Parameter]из-за затрат на отслеживания подписки. - Если параметр
IsFixedимеет значениеtrue(например,<CascadingValue Value="someValue" IsFixed="true">), получатели получают начальное значение, но не настраивают подписку для получения обновлений. Каждый[CascadingParameter]достаточно легкий, но не дороже, чем обычный[Parameter].
Присвоение параметру IsFixed значения true позволит увеличить производительность, если большое число других компонентов получает это каскадное значение. Соответственно, для каскадных значений следует по возможности присваивать параметру IsFixed значение true. Присваивать параметру IsFixed значение true можно в тех случаях, когда предоставленное значение не изменяется со временем.
Когда компонент передает this как каскадное значение, IsFixed также можно установить на true, поскольку this никогда не меняется в течение жизненного цикла компонента.
<CascadingValue Value="this" IsFixed="true">
<SomeOtherComponents>
</CascadingValue>
Дополнительные сведения см. в статье Каскадные значения и параметры ASP.NET Core Blazor.
Не используйте сплаттинг атрибутов с CaptureUnmatchedValues
Компоненты могут получать "несопоставленные" значения параметров с помощью флага CaptureUnmatchedValues:
<div @attributes="OtherAttributes">...</div>
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? OtherAttributes { get; set; }
}
Такой подход позволяет передавать элементу произвольные дополнительные атрибуты. Тем не менее он сопряжен с достаточно высокими затратами ресурсов, поскольку отрисовщик будет выполнять следующие действия.
- сопоставление всех полученных параметров с набором известных параметров для построения словаря;
- Следите за тем, как несколько копий одного атрибута заменяют друг друга.
Используйте CaptureUnmatchedValues в тех случаях, когда производительность отрисовки компонентов не играет критической роли, например для редко повторяющихся компонентов. Для компонентов, которые выводятся в большом масштабе, например, для каждого элемента большого списка или ячейки сетки, старайтесь избегать использования технологии распыления атрибутов.
Для получения дополнительной информации см. ASP.NET Core Blazor разбрызгивание атрибутов и произвольные параметры.
Реализуйте SetParametersAsync вручную
Одним из важнейших аспектов, влияющих на затраты ресурсов для отрисовки каждого компонента, является запись значений входящих параметров в свойства [Parameter]. Отрисовщик использует рефлексию для записи значений параметров, что может привести к снижению производительности в масштабе.
В некоторых крайних случаях следует избегать отражения и вручную реализовать собственную логику настройки параметров. Это может уместно в следующих случаях:
- компонент отображается чрезвычайно часто (например, существуют сотни или тысячи его копий в пользовательском интерфейсе);
- компонент принимает множество параметров;
- вы заметили, что издержки на получение параметров заметно влияют на скорость реагирования пользовательского интерфейса.
В крайнем случае вы можете переопределить виртуальный метод SetParametersAsync компонента и реализовать собственную логику для конкретного компонента. Следующий пример намеренно создан так, чтобы избежать поиска в словаре.
@code {
[Parameter]
public int MessageId { get; set; }
[Parameter]
public string? Text { get; set; }
[Parameter]
public EventCallback<string> TextChanged { get; set; }
[Parameter]
public Theme CurrentTheme { get; set; }
public override Task SetParametersAsync(ParameterView parameters)
{
foreach (var parameter in parameters)
{
switch (parameter.Name)
{
case nameof(MessageId):
MessageId = (int)parameter.Value;
break;
case nameof(Text):
Text = (string)parameter.Value;
break;
case nameof(TextChanged):
TextChanged = (EventCallback<string>)parameter.Value;
break;
case nameof(CurrentTheme):
CurrentTheme = (Theme)parameter.Value;
break;
default:
throw new ArgumentException($"Unknown parameter: {parameter.Name}");
}
}
return base.SetParametersAsync(ParameterView.Empty);
}
}
В приведенном выше коде возврат базового класса SetParametersAsync запускает обычный метод жизненного цикла без повторного назначения параметров.
Как мы видим в приведенном выше коде, переопределение SetParametersAsync и предоставление пользовательской логики достаточно сложны и трудоемки, поэтому в общем случае мы не рекомендуем использовать этот подход. В крайних случаях подход может обеспечить небольшое улучшение производительности отрисовки, как правило, менее чем примерно на 10%, даже при 10 000 экземпляров компонентов на целевой платформе .NET 10. Потенциальные выгоды меньше в последующих выпусках, так как назначение параметров на основе отражения оптимизировано. Рассматривайте этот подход только в крайних сценариях, перечисленных ранее в этом разделе, и сначала проведите тестирование — экономия обычно незначительна по сравнению с другими затратами, например, с перемещением данных SignalRдиффа (изменений DOM) для интерактивного сервера.
Как мы видим в приведенном выше коде, переопределение SetParametersAsync и предоставление пользовательской логики достаточно сложны и трудоемки, поэтому в общем случае мы не рекомендуем использовать этот подход. В крайних случаях подход может повысить производительность отрисовки до 20–25%, но этот подход следует учитывать только в крайних сценариях, перечисленных ранее в этом разделе.
Не активируйте события слишком быстро
Некоторые события браузера возникают очень часто. Например, события onmousemove и onscroll могут возникать десятки или сотни раз в секунду. В большинстве случаев вам не нужно так часто обновлять интерфейс. Если события возникают слишком часто, это может снизить скорость реагирования пользовательского интерфейса или увеличить загрузку ЦП.
Вместо использования встроенных событий, которые быстро запускаются, рекомендуется использовать интероперабельность JS для регистрации обратного вызова с меньшей частотой. Например, следующий компонент отображает текущее положение мыши только один раз в каждые 500 мс.
@implements IDisposable
@inject IJSRuntime JS
<h1>@message</h1>
<div @ref="mouseMoveElement" style="border:1px dashed red;height:200px;">
Move mouse here
</div>
@code {
private ElementReference mouseMoveElement;
private DotNetObjectReference<MyComponent>? selfReference;
private string message = "Move the mouse in the box";
[JSInvokable]
public void HandleMouseMove(int x, int y)
{
message = $"Mouse move at {x}, {y}";
StateHasChanged();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
selfReference = DotNetObjectReference.Create(this);
var minInterval = 500;
await JS.InvokeVoidAsync("onThrottledMouseMove",
mouseMoveElement, selfReference, minInterval);
}
}
public void Dispose() => selfReference?.Dispose();
}
Соответствующий код JavaScript регистрирует прослушиватель событий DOM для перемещения мыши. В нашем примере прослушиватель событий использует функцию Lodash throttle, чтобы ограничить частоту вызовов:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
<script>
function onThrottledMouseMove(elem, component, interval) {
elem.addEventListener('mousemove', _.throttle(e => {
component.invokeMethodAsync('HandleMouseMove', e.offsetX, e.offsetY);
}, interval));
}
</script>
Избегайте повторной отрисовки после обработки событий без изменения состояния
Компоненты наследуются от ComponentBase, который автоматически вызывает StateHasChanged после того, как обработчики событий компонента были вызваны. В некоторых случаях может оказаться ненужным или нежелательным вызывать повторную отрисовку после вызова обработчика событий. Например, возможно, обработчик событий не изменил состояние компонента. В таких случаях приложение может использовать интерфейс IHandleEvent для управления поведением обработки событий Blazor.
Замечание
Подход в этом разделе не передает исключения в границы ошибок. Для получения дополнительной информации и кода демонстрации, который поддерживает границы ошибок путем вызова ComponentBase.DispatchExceptionAsync, см. раздел AsNonRenderingEventHandler + ErrorBoundary = непредвиденное поведение (dotnet/aspnetcore #54543).
Чтобы предотвратить повторную отрисовку для всех обработчиков событий компонента, следует реализовать IHandleEvent и предоставить задачу IHandleEvent.HandleEventAsync, которая вызывает обработчик событий без вызова StateHasChanged.
В следующем примере ни один из обработчиков событий, добавленных к компоненту, не активирует повторную отрисовку, поэтому HandleSelect не приводит к повторной отрисовке при вызове.
HandleSelect1.razor:
@page "/handle-select-1"
@using Microsoft.Extensions.Logging
@implements IHandleEvent
@inject ILogger<HandleSelect1> Logger
<p>
Last render DateTime: @dt
</p>
<button @onclick="HandleSelect">
Select me (Avoids Rerender)
</button>
@code {
private DateTime dt = DateTime.Now;
private void HandleSelect()
{
dt = DateTime.Now;
Logger.LogInformation("This event handler doesn't trigger a rerender.");
}
Task IHandleEvent.HandleEventAsync(
EventCallbackWorkItem callback, object? arg) => callback.InvokeAsync(arg);
}
Помимо предотвращения глобального повторного рендеринга после срабатывания обработчиков событий в компоненте, можно предотвратить повторный рендеринг после одного обработчика событий, применив следующий служебный метод.
Добавьте следующий класс EventUtil в приложение Blazor. Статические действия и функции в верхней части класса EventUtil предоставляют обработчики, охватывающие несколько сочетаний аргументов и возвращаемых типов, которые Blazor использует при обработке событий.
EventUtil.cs:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public static class EventUtil
{
public static Action AsNonRenderingEventHandler(Action callback)
=> new SyncReceiver(callback).Invoke;
public static Action<TValue> AsNonRenderingEventHandler<TValue>(
Action<TValue> callback)
=> new SyncReceiver<TValue>(callback).Invoke;
public static Func<Task> AsNonRenderingEventHandler(Func<Task> callback)
=> new AsyncReceiver(callback).Invoke;
public static Func<TValue, Task> AsNonRenderingEventHandler<TValue>(
Func<TValue, Task> callback)
=> new AsyncReceiver<TValue>(callback).Invoke;
private record SyncReceiver(Action callback)
: ReceiverBase { public void Invoke() => callback(); }
private record SyncReceiver<T>(Action<T> callback)
: ReceiverBase { public void Invoke(T arg) => callback(arg); }
private record AsyncReceiver(Func<Task> callback)
: ReceiverBase { public Task Invoke() => callback(); }
private record AsyncReceiver<T>(Func<T, Task> callback)
: ReceiverBase { public Task Invoke(T arg) => callback(arg); }
private record ReceiverBase : IHandleEvent
{
public Task HandleEventAsync(EventCallbackWorkItem item, object arg) =>
item.InvokeAsync(arg);
}
}
Вызовите метод EventUtil.AsNonRenderingEventHandler, чтобы вызвать обработчик событий, который не запускает отрисовку при вызове.
В следующем примере :
- При нажатии первой кнопки, которая вызывает
HandleClick1, повторная отрисовка запускается. - При нажатии второй кнопки, которая вызывает
HandleClick2, повторная отрисовка не запускается. - При нажатии третьей кнопки, которая вызывает
HandleClick3, повторная отрисовка не запускается и используются аргументы события (MouseEventArgs).
HandleSelect2.razor:
@page "/handle-select-2"
@using Microsoft.Extensions.Logging
@inject ILogger<HandleSelect2> Logger
<p>
Last render DateTime: @dt
</p>
<button @onclick="HandleClick1">
Select me (Rerenders)
</button>
<button @onclick="EventUtil.AsNonRenderingEventHandler(HandleClick2)">
Select me (Avoids Rerender)
</button>
<button @onclick="EventUtil.AsNonRenderingEventHandler<MouseEventArgs>(HandleClick3)">
Select me (Avoids Rerender and uses <code>MouseEventArgs</code>)
</button>
@code {
private DateTime dt = DateTime.Now;
private void HandleClick1()
{
dt = DateTime.Now;
Logger.LogInformation("This event handler triggers a rerender.");
}
private void HandleClick2()
{
dt = DateTime.Now;
Logger.LogInformation("This event handler doesn't trigger a rerender.");
}
private void HandleClick3(MouseEventArgs args)
{
dt = DateTime.Now;
Logger.LogInformation(
"This event handler doesn't trigger a rerender. " +
"Mouse coordinates: {ScreenX}:{ScreenY}",
args.ScreenX, args.ScreenY);
}
}
Помимо реализации интерфейса IHandleEvent, использование других рекомендаций, описанных в этой статье, также может помочь уменьшить количество нежелательных отрисовок после обработки событий. Например, переопределение ShouldRender в дочерних компонентах целевого компонента можно использовать для управления повторной отрисовкой.
Избегайте повторного создания делегатов для большого числа повторяющихся элементов или компонентов
Воссоздание делегатов лямбда-выражений Blazor для элементов или компонентов в цикле может привести к снижению производительности.
Следующий компонент, представленный в статье об обработке событий, отвечает за отрисовку набора кнопок. Каждая кнопка назначает обработчик для события @onclick, что допустимо, если кнопок для отрисовки немного.
EventHandlerExample5.razor:
@page "/event-handler-example-5"
<h1>@heading</h1>
@for (var i = 1; i < 4; i++)
{
var buttonNumber = i;
<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
private void UpdateHeading(MouseEventArgs e, int buttonNumber)
{
heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}
}
@page "/event-handler-example-5"
<h1>@heading</h1>
@for (var i = 1; i < 4; i++)
{
var buttonNumber = i;
<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
private void UpdateHeading(MouseEventArgs e, int buttonNumber)
{
heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}
}
Если использовать описанный выше подход для отрисовки большого количества кнопок, скорость отрисовки будет крайне низкой. Это ухудшит взаимодействие с пользователем. Чтобы отрисовать большое количество кнопок с обратным вызовом для событий щелчка, в приведенном ниже примере используется коллекция объектов кнопок, которые позволяют присвоить делегату @onclick для каждой кнопки значение Action. Описанный ниже подход позволяет Blazor не перестраивать все делегаты кнопок при каждой отрисовке:
LambdaEventPerformance.razor:
@page "/lambda-event-performance"
<h1>@heading</h1>
@foreach (var button in Buttons)
{
<p>
<button @key="button.Id" @onclick="button.Action">
Button #@button.Id
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
private List<Button> Buttons { get; set; } = new();
protected override void OnInitialized()
{
for (var i = 0; i < 100; i++)
{
var button = new Button();
button.Id = Guid.NewGuid().ToString();
button.Action = (e) =>
{
UpdateHeading(button, e);
};
Buttons.Add(button);
}
}
private void UpdateHeading(Button button, MouseEventArgs e)
{
heading = $"Selected #{button.Id} at {e.ClientX}:{e.ClientY}";
}
private class Button
{
public string? Id { get; set; }
public Action<MouseEventArgs> Action { get; set; } = e => { };
}
}
ASP.NET Core