Blazor с нуля: Глава 3 — Компоненты, которые масштабируются
Добро пожаловать в главу 3 серии Blazor с нуля. Если вы пропустили главу 2, лучше сначала прочитать её, чтобы базовый проект уже был готов.
В главе 2 мы запустили приложение. В этой главе сделаем его поддерживаемым.
Blazor-проекты быстро становятся запутанными, когда каждая страница превращается в огромный .razor-файл. Компоненты помогают этого избежать: дают единообразие, переиспользование и чёткие границы между частями UI.
Что такое компонент Blazor на практике
Компонент — это .razor-файл, который умеет:
- Рендерить разметку
- Хранить локальное состояние
- Принимать входные данные через параметры
- Отправлять события в родительский компонент
- Рендерить дочернее содержимое
Во время выполнения Blazor рассматривает компонент как маленькую машину состояний. При изменении состояния выполняется повторный рендер и применяется diff DOM.
Поэтому ваша задача — проектировать ясные входы и предсказуемое поведение.
Шаг 1: Начните с узко сфокусированного компонента
Создайте Components/Common/SectionHeader.razor:
<header class="section-header">
<h2>@Title</h2>
@if (!string.IsNullOrWhiteSpace(Subtitle))
{
<p>@Subtitle</p>
}
</header>
@code {
[Parameter] public string Title { get; set; } = string.Empty;
[Parameter] public string? Subtitle { get; set; }
}
Использование в Components/Pages/Home.razor:
@page "/"
<PageTitle>Blazor с нуля</PageTitle>
<SectionHeader
Title="Blazor с нуля"
Subtitle="Глава 3 посвящена компонентам." />
Пример маленький, но идея важная: компонент должен быть понятен по своим параметрам.
Шаг 2: Делайте поведение явным через параметры
Создадим переиспользуемую кнопку.
Components/Common/AppButton.razor:
<button class="app-button @VariantCssClass" @onclick="OnClick">
@Text
</button>
@code {
[Parameter] public string Text { get; set; } = "Button";
[Parameter] public string Variant { get; set; } = "primary";
[Parameter] public EventCallback OnClick { get; set; }
private string VariantCssClass => Variant.ToLowerInvariant() switch
{
"secondary" => "app-button--secondary",
"danger" => "app-button--danger",
_ => "app-button--primary"
};
}
Пример использования:
@code {
private int _savedCount;
private void Save()
{
_savedCount++;
}
}
<AppButton Text="Save" Variant="primary" OnClick="Save" />
<p>Сохранено @_savedCount раз.</p>
Ключевой момент — дизайн контракта:
- Мало, но чётко определённых параметров
- Явные имена вместо “магического” поведения
- Безопасные значения по умолчанию
Шаг 3: Используйте RenderFragment для композиции
RenderFragment позволяет родителю передавать блоки UI в дочерний компонент.
Создайте Components/Common/Card.razor:
<article class="card">
<header class="card__header">@Title</header>
<section class="card__body">
@ChildContent
</section>
</article>
@code {
[Parameter] public string Title { get; set; } = string.Empty;
[Parameter] public RenderFragment? ChildContent { get; set; }
}
Пример:
<Card Title="Roadmap">
<ul>
<li>Компоненты</li>
<li>Data binding</li>
<li>Routing</li>
</ul>
</Card>
Этот подход помогает строить единообразные страницы без копирования обёрток.
Шаг 4: Композиция вместо гигантских страниц
Если страница разрастается, разделяйте по ответственности:
ProfileSummaryдля верхнего блока профиляProfileStatsдля метрикProfileActivityListдля последних действий
Тогда страница становится точкой оркестрации, а не контейнером всех деталей.
Шаг 5: Баланс между разметкой и логикой
Для простых компонентов inline @code — нормально.
Для крупных компонентов лучше вынести логику в code-behind:
UserCard.razorUserCard.razor.cs
Так и разметка, и C#-логика остаются читаемыми.
Шаг 6: Практичная структура папок
Структура, которая хорошо масштабируется:
Components/Pages/-> маршрутизируемые страницыComponents/Layout/-> shell приложения и навигацияComponents/Common/-> общие универсальные блокиComponents/Features/<FeatureName>/-> компоненты по функциональности
Частые ошибки в ранних Blazor-проектах
- Слишком много параметров вместо отдельной модели
- Бизнес-правила прямо в page-компонентах
- Один “god component” на сотни строк
- Дублирование разметки вместо выделения переиспользуемых частей
Что дальше
В главе 4 разберём data binding и события: @bind, обработку событий, компромиссы two-way binding и паттерны предсказуемого состояния.