Blazor をゼロから: 第3章 — スケールするコンポーネント設計

· 2分で読める

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.razor
  • UserCard.razor.cs

UI と C# ロジックの可読性がどちらも上がります。


ステップ6: 実用的なフォルダー構成

スケールしやすい構成の例:

  • Components/Pages/ -> ルーティング対象ページ
  • Components/Layout/ -> アプリ共通レイアウトとナビ
  • Components/Common/ -> 汎用の共有コンポーネント
  • Components/Features/<FeatureName>/ -> 機能別コンポーネント

初期の Blazor プロジェクトでよくあるミス

  • 専用モデルを作らず、パラメーターを増やしすぎる
  • ページコンポーネントに業務ロジックを入れすぎる
  • 数百行の “God Component” を作る
  • マークアップを繰り返し、共通化しない

次回予告

第4章では データバインディングとイベント を扱います。@bind、イベント処理、双方向バインディングのトレードオフ、予測可能な状態管理パターンを見ていきます。