Blazor od podstaw: Rozdział 3 — Komponenty, które skalują się z projektem
Witamy w Rozdziale 3 serii Blazor od podstaw. Jeśli pominąłeś Rozdział 2, wróć do niego najpierw, żeby mieć gotowy projekt bazowy.
W Rozdziale 2 uruchomiliśmy aplikację. W tym rozdziale sprawimy, że będzie utrzymywalna.
Aplikacje Blazor szybko robią się chaotyczne, gdy każda strona staje się ogromnym plikiem .razor. Komponenty pomagają tego uniknąć: dają spójność, reużywalność i czytelne granice między fragmentami UI.
Czym naprawdę jest komponent Blazor
Komponent to plik .razor, który potrafi:
- Renderować markup
- Trzymać lokalny stan
- Przyjmować dane przez parametry
- Wysyłać zdarzenia do komponentu nadrzędnego
- Renderować treść potomną
W runtime Blazor traktuje komponent jak małą maszynę stanów. Gdy stan się zmienia, framework renderuje ponownie i stosuje diff DOM.
Dlatego warto projektować jasne wejścia i przewidywalne zachowanie.
Krok 1: Zacznij od jednego, konkretnego komponentu
Utwórz 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; }
}
Użycie w Components/Pages/Home.razor:
@page "/"
<PageTitle>Blazor od podstaw</PageTitle>
<SectionHeader
Title="Blazor od podstaw"
Subtitle="Rozdział 3 jest o komponentach." />
To mały przykład, ale pokazuje kluczową zasadę: komponent powinien być czytelny przez same parametry.
Krok 2: Parametry jako jawny kontrakt
Stwórzmy reużywalny przycisk.
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"
};
}
Użycie:
@code {
private int _savedCount;
private void Save()
{
_savedCount++;
}
}
<AppButton Text="Save" Variant="primary" OnClick="Save" />
<p>Zapisano @_savedCount razy.</p>
Najważniejszy jest kontrakt:
- Mało parametrów, ale konkretnych
- Jawne nazwy zamiast ukrytej “magii”
- Bezpieczne wartości domyślne
Krok 3: RenderFragment do kompozycji layoutu
RenderFragment pozwala rodzicowi przekazać blok UI do komponentu dziecka.
Utwórz 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; }
}
Użycie:
<Card Title="Roadmap">
<ul>
<li>Komponenty</li>
<li>Data binding</li>
<li>Routing</li>
</ul>
</Card>
To wzorzec, który pozwala budować spójny layout bez duplikowania wrapperów.
Krok 4: Kompozycja zamiast gigantycznych stron
Jeśli strona rośnie za bardzo, rozdziel ją wg odpowiedzialności:
ProfileSummarydla części nagłówkowejProfileStatsdla metrykProfileActivityListdla ostatniej aktywności
Wtedy strona staje się orkiestracją, a nie miejscem na wszystkie detale.
Krok 5: Równowaga między markupem a logiką
Dla prostych komponentów inline @code jest w porządku.
Dla większych komponentów przenieś logikę do code-behind:
UserCard.razorUserCard.razor.cs
Dzięki temu zarówno markup, jak i logika C# są czytelniejsze.
Krok 6: Praktyczna struktura folderów
Struktura, która zwykle dobrze działa:
Components/Pages/-> strony routowalneComponents/Layout/-> shell aplikacji i nawigacjaComponents/Common/-> współdzielone, generyczne klockiComponents/Features/<FeatureName>/-> komponenty specyficzne dla funkcji
Najczęstsze błędy na początku
- Za dużo parametrów zamiast dedykowanego modelu widoku
- Reguły biznesowe upchnięte w komponentach stron
- Jeden “boski komponent” z setkami linii
- Powielanie tych samych fragmentów markupu zamiast ekstrakcji komponentów
Co dalej
W Rozdziale 4 skupimy się na data binding i zdarzeniach: @bind, obsługa zdarzeń, kompromisy two-way binding i wzorce utrzymujące przewidywalny stan.