Blazor をゼロから: 第3章 — スケールするコンポーネント設計
Blazor をゼロから 第3章へようこそ。まだ 第2章 を読んでいない場合は、先にそちらを読むとスムーズです。
第2章ではアプリを動かしました。第3章ではそれを 保守しやすい構造 にします。
Blazor は、各ページが巨大な .razor ファイルになると一気に複雑化します。そこで重要なのがコンポーネントです。再利用性、一貫性、責務の分離を実現できます。
Blazor コンポーネントとは何か
コンポーネントは、次のことができる .razor ファイルです。
- マークアップを描画する
- ローカル状態を持つ
- パラメーターで入力を受け取る
- 親コンポーネントへイベントを通知する
- 子コンテンツを描画する
実行時、Blazor は各コンポーネントを小さな状態マシンとして扱います。状態が変わると再描画し、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: マークアップとロジックのバランスを取る
小さいコンポーネントなら @code のインラインで十分です。
大きい場合は code-behind に分けます。
UserCard.razorUserCard.razor.cs
UI と C# ロジックの可読性がどちらも上がります。
ステップ6: 実用的なフォルダー構成
スケールしやすい構成の例:
Components/Pages/-> ルーティング対象ページComponents/Layout/-> アプリ共通レイアウトとナビComponents/Common/-> 汎用の共有コンポーネントComponents/Features/<FeatureName>/-> 機能別コンポーネント
初期の Blazor プロジェクトでよくあるミス
- 専用モデルを作らず、パラメーターを増やしすぎる
- ページコンポーネントに業務ロジックを入れすぎる
- 数百行の “God Component” を作る
- マークアップを繰り返し、共通化しない
次回予告
第4章では データバインディングとイベント を扱います。@bind、イベント処理、双方向バインディングのトレードオフ、予測可能な状態管理パターンを見ていきます。