Расскажите о случае, когда вы улучшали performance, accessibility, testing или maintainability в проекте на Blazor.
В реальном Blazor-проекте улучшения достигаются через виртуализацию списков (Virtualize), aria-атрибуты доступности, bUnit-тесты компонентов и разделение логики через сервисы.
Улучшение качества Blazor-проекта: реальный опыт
В одном из проектов мы столкнулись с несколькими проблемами одновременно: медленная прокрутка списка из 10 000 элементов, отсутствие доступности для скринридеров, отсутствие тестового покрытия компонентов и монолитные компоненты на 500+ строк. Ниже — конкретные шаги, которые мы предприняли.
Performance: виртуализация списков
Компонент <Virtualize>, доступный с .NET 6, рендерит только видимые элементы. Мы заменили @foreach на виртуализацию и получили ускорение рендеринга с 1200 мс до 80 мс на списке из 5000 элементов:
<Virtualize Items="_jobs" Context="job" OverscanCount="5">
<ItemContent>
<JobCard Job="job" />
</ItemContent>
<Placeholder>
<JobCardSkeleton />
</Placeholder>
</Virtualize>
Для серверных данных использовали перегрузку с ItemsProvider, чтобы подгружать страницы по требованию:
private async ValueTask<ItemsProviderResult<Job>> LoadJobs(
ItemsProviderRequest request)
{
var page = await _jobService.GetPageAsync(request.StartIndex, request.Count);
return new ItemsProviderResult<Job>(page.Items, page.TotalCount);
}
Accessibility: ARIA и навигация с клавиатуры
Аудит с Axe DevTools выявил: кнопки без aria-label, диалоговые окна без role="dialog", отсутствие focus trap в модальных окнах. Исправления:
<button @onclick="OpenModal"
aria-haspopup="dialog"
aria-expanded="@_isOpen"
aria-controls="details-modal">
Подробнее
</button>
<div id="details-modal"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
tabindex="-1">
<h2 id="modal-title">Детали вакансии</h2>
<!-- контент -->
</div>
Testing: bUnit
Компонентные тесты писали с помощью библиотеки bUnit:
using Bunit;
using Xunit;
public class JobCardTests : TestContext
{
[Fact]
public void JobCard_ShowsSalary_WhenProvided()
{
var job = new Job { Title = "Dev", Salary = "$5000" };
var cut = RenderComponent<JobCard>(
parameters => parameters.Add(p => p.Job, job));
cut.Find(".salary").MarkupMatches("<span class=\"salary\">$5000</span>");
}
[Fact]
public void JobCard_HidesSalary_WhenNull()
{
var job = new Job { Title = "Dev", Salary = null };
var cut = RenderComponent<JobCard>(
parameters => parameters.Add(p => p.Job, job));
Assert.Empty(cut.FindAll(".salary"));
}
}
Maintainability: разделение компонентов
Монолитный компонент разбили по принципу «один компонент — одна ответственность», логику вынесли в сервисы с интерфейсами, чтобы заменять на моки в тестах. Использовали partial class для разделения разметки и логики:
// JobList.razor.cs
public partial class JobList
{
[Inject] private IJobService JobService { get; set; } = default!;
private List<Job> _jobs = [];
protected override async Task OnInitializedAsync()
{
_jobs = await JobService.GetAsync();
}
}
Подводные камни
Virtualizeтребует точногоTotalItemCountпри использованииItemsProvider— ошибка на единицу приводит к пустым строкам в конце списка.- bUnit не тестирует JavaScript interop напрямую — нужно мокировать
IJSRuntimeчерезctx.JSInterop.Setup. - ARIA-атрибуты на Blazor-компонентах передаются через
AdditionalAttributesили явные параметры — не забудьте добавить[Parameter(CaptureUnmatchedValues = true)]в базовые компоненты. - Разбивка на partial class не разграничивает зависимости — без DI-интерфейсов тесты всё равно потребуют реальных зависимостей.
- В Blazor Server Virtualize с большим
OverscanCountувеличивает нагрузку на SignalR-канал — держите значение минимальным (2–5).
What hurts your answer
- Выдумывать опыт или говорить слишком общими фразами
- Не объяснять свою личную роль в работе с Blazor
- Не показывать результат, метрики или извлечённые уроки
What they're listening for
- Может подготовить честный пример использования Blazor
- Показывает свою роль, решения и результат
- Умеет рефлексировать над trade-offs и уроками