Blazor do zero: Capítulo 3 — Componentes que escalam
Bem-vindo ao Capítulo 3 de Blazor do zero. Se você não leu o Capítulo 2, leia primeiro para já ter o projeto base pronto.
No Capítulo 2 fizemos a aplicação rodar. Neste capítulo vamos deixá-la sustentável.
Projetos Blazor ficam bagunçados rápido quando cada página vira um .razor gigante. Componentes são a forma de evitar isso: trazem consistência, reutilização e limites claros entre partes da interface.
O que um componente Blazor realmente é
Um componente é um arquivo .razor que pode:
- Renderizar markup
- Manter estado local
- Receber dados via parâmetros
- Emitir eventos para o componente pai
- Renderizar conteúdo filho
Em execução, o Blazor trata cada componente como uma pequena máquina de estado. Quando o estado muda, ele renderiza novamente e aplica um diff no DOM.
Por isso você deve desenhar entradas limpas e comportamento previsível.
Passo 1: Comece com um componente focado
Crie 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; }
}
Use em Components/Pages/Home.razor:
@page "/"
<PageTitle>Blazor do zero</PageTitle>
<SectionHeader
Title="Blazor do zero"
Subtitle="O capítulo 3 é sobre componentes." />
É um exemplo simples, mas introduz a ideia central: os parâmetros devem explicar o componente.
Passo 2: Use parâmetros para deixar o comportamento explícito
Vamos criar um botão reutilizável.
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"
};
}
Uso:
@code {
private int _savedCount;
private void Save()
{
_savedCount++;
}
}
<AppButton Text="Save" Variant="primary" OnClick="Save" />
<p>Salvo @_savedCount vezes.</p>
O ponto principal é o contrato:
- Parâmetros pequenos e claros
- Nomes explícitos, sem comportamento “mágico”
- Defaults seguros
Passo 3: Use RenderFragment para composição
RenderFragment permite que o componente pai injete blocos de UI no filho.
Crie 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; }
}
Uso:
<Card Title="Roadmap">
<ul>
<li>Componentes</li>
<li>Data binding</li>
<li>Routing</li>
</ul>
</Card>
Esse padrão evita repetir o mesmo markup de estrutura em várias páginas.
Passo 4: Prefira composição em vez de páginas gigantes
Se a página crescer demais, separe por responsabilidade:
ProfileSummarypara o bloco principalProfileStatspara métricasProfileActivityListpara atividades recentes
Assim a página vira orquestração e não um bloco monolítico.
Passo 5: Equilibre markup e lógica
Para componentes simples, @code inline funciona bem.
Para componentes maiores, mova lógica para code-behind:
UserCard.razorUserCard.razor.cs
Isso melhora a leitura do markup e da lógica C#.
Passo 6: Estrutura de pastas prática
Uma organização que costuma escalar bem:
Components/Pages/-> páginas roteáveisComponents/Layout/-> shell e navegaçãoComponents/Common/-> blocos genéricos compartilhadosComponents/Features/<FeatureName>/-> componentes de cada feature
Erros comuns no início com Blazor
- Passar parâmetros demais em vez de usar um view model dedicado
- Colocar regra de negócio dentro de componentes de página
- Criar um “componente Deus” com centenas de linhas
- Repetir padrões de markup em vez de extrair peças reutilizáveis
Próximo capítulo
No Capítulo 4 vamos focar em data binding e eventos: @bind, tratamento de eventos, tradeoffs do two-way binding e padrões para manter estado previsível.