Blazor с нуля: Глава 3 — Компоненты, которые масштабируются

· 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.razor
  • UserCard.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 и паттерны предсказуемого состояния.