Blazorの最も重要な概念であるコンポーネントの基本的な仕組みや使い方を解説します。
環境
- Windows 11
- Visual Studio 2022
- .NET 8
前提
各サンプルコードはInteractive render modeにServer(Blazor Server)、Interactive locationにPer page/component(ページ/コンポーネント単位でレンダーモードを指定可能)を選択した場合のものです。
他のレンダーモードの場合は表記や挙動が異なる場合があるのでご注意ください。
コンポーネントとは
Blazorアプリケーションを構成する基本要素で、HTML, CSS, C#などを組み合わせて実装されます。
主な特徴
- コンポーネント名は必ずパスカルケース(例:TodoList.razor)
- 拡張子は
.razor
- Razor 構文を使用
- 暗黙的にComponentBaseクラスを継承する
- Blazorに標準で組み込まれているコンポーネントもある
参考:ASP.NET Core 組み込み Razor コンポーネント
基本構成
プロジェクト作成時(「Include sample pages」を選択した場合)に最初から用意されているCounter.razorを見てみます。
@page "/counter"
@rendermode InteractiveServer
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
@page
ディレクティブはコンポーネントのパスを指定するものです。
@rendermode
ディレクティブはレンダリングモードを指定するもので、InteractiveServer
は「Blazor Serverでの対話型サーバー レンダリング」を有効化します。
対話型とは、ユーザーの操作や入力に対してアプリ側が即座に反応を返すことで相互にやりとりできる方式のことで、ボタンクリックやテキスト入力時にすぐに表示が変わる処理などが当てはまります。
参考:ASP.NET Core Blazor のレンダー モード
<PageTitle>
は組み込みのコンポーネントで、その名の通りページのタイトルを指定できます。
@code
ブロックはコンポーネントのC#ロジックを記述する場所で、今回は2つの要素があります。
currentCount
フィールド:現在のカウントの値を保持するプライベート変数で、Razor(HTML)側からは@currentCount
のように@変数名
で参照することができます。IncrementCount
メソッド:currentCount
を1増やすプライベート関数で、ここではbuttonのクリックイベント時に呼び出されます。(イベント時に呼び出されるメソッドをイベントハンドラーといいます)
イベントハンドラー(IncrementCount
メソッド)が実行されると画面の更新(レンダリング)が発生するため、更新後のcurrentCount
の値がすぐに画面に反映されます。
RazorとC#のファイル分割
先ほどは1つのrazorファイルにRazorマークアップ部分とC#コード部分がまとまっていましたが、部分クラスを使うとそれらを分割することができます。
Counter.razorの @code
ブロック部分を部分クラスに切り出したのが以下のコードです。
@page "/counter"
@rendermode InteractiveServer
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
namespace ComponentSample.Components.Pages
{
public partial class Counter
{
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
}
C#コード部分は別途Counter.razor.csファイルを作成し、その中にpartialクラスを定義します。
中身は分割前の
ブロックに書かれていた内容と同じです。@code
開発プロジェクトの方針などに合わせて分割するかしないかを使い分けてみてください。
_Imports.razor
ファイルの @using
ディレクティブは、C#ファイル (.cs) ではなくRazorファイル (.razor) にのみ適用されるので注意してください。
他のコンポーネントを呼び出す
コンポーネントから他のコンポーネントを呼び出すことができます。
説明のため子コンポーネントと親コンポーネントをそれぞれ定義します。
<h3>子コンポーネントです</h3>
メッセージだけを表示する非常にシンプルなコンポーネントです。
C#のロジックはないので@code
ブロックは使用していません。
@page "/parent"
<h2>親コンポーネントです</h2>
<ChildComponent />
<ChildComponent />
<ChildComponent />
他のコンポーネントを呼び出すには、HTMLタグと同じように記述します。
今回、親コンポーネントでは子コンポーネントを3回呼び出しています。
アプリを実行して/parent
にアクセスしてみると、親コンポーネントから子コンポーネントが3回呼ばれていることがわかります。
他のコンポーネントにパラメーターを渡す
他のコンポーネントに値を渡すこともできます。
先ほどのコンポーネントを修正し、親コンポーネントから子コンポーネントに値を渡せるようにしました。
独自クラスも受け渡しができることを示すため、適当なクラスを定義します。
namespace ComponentSample.Components.Pages
{
public class Person
{
public string? Name { get; set; }
public int Age { get; set; }
}
}
子コンポーネントでは親からstring型のメッセージと、定義したPerson型のモデルが受け取れるようにしました。受け取ったパラメータはそのまま画面に表示されます。
<h3>子コンポーネントです</h3>
<p>@Message</p>
<ul>
<li>@Model.Name</li>
<li>@Model.Age</li>
</ul>
@code {
[Parameter]
public string Message { get; set; } = "子コンポーネントで設定したメッセージ";
[Parameter]
public Person Model { get; set; } = new() { Name = "子コンポーネントで設定した名前", Age = 0 };
}
なお、パラメーターを受け取るためには下記のルールがあります。
- [Parameter]属性を付与すること
- Publicプロパティであること
- 自動実装プロパティで、アクセサはget, setであること
パラメーターを受け取るプロパティに初期値設定以外のロジックを含めるのは、レンダリングの無限ループに陥る可能性があるためNGです。
受け取ったパラメーターを変更した場合は、そのプロパティではなく他の変数等に渡すようにしましょう。
参考:ASP.NET Core Blazor でパラメーターを上書きしないようにする
親コンポーネントでは、子コンポーネントの呼び出し方法を変更しています。
1つ目は引数なし、2つ目はMessageのみを指定、3つ目はModelのみを指定して子コンポーネントを呼び出します。
@page "/parent"
<h2>親コンポーネントです</h2>
<ChildComponent />
<ChildComponent Message="親コンポーネントから渡したメッセージ"/>
<ChildComponent Model="person" />
@code{
private Person person = new() { Name = "親コンポーネントから渡した名前", Age = 30 };
}
実行すると、親コンポーネントからパラメーターが渡された場合はその内容が表示され、渡されていない場合は子コンポーネントの初期値が表示されることがわかります。
ルートパラメーターとルート制約
パスの一部としてパラメーター(ルートパラメーター)を受け取る場合は、{}
の中にパラメーター名を記述します。
また、コロンの後に制約(ルート制約)を書くことができ、今回のようにintとするとint型のパラメーターのみを受け取れるようになります。
@page "/route-parameter1/{number:int}"
<h3>ルートパラメーターとルート制約1</h3>
<p>@Number</p>
@code {
[Parameter]
public int Number { get; set; }
}
numberの部分に数値を指定してアクセスすると、その値がそのまま画面に表示されます。
int型でない値を指定した場合や、何も指定が無い場合は404エラーとなります。
省略可能なパラメーターを設定する場合は、パラメーター名の後ろに?を付けます。
このコードでは、パラメーターが指定されていない場合は初期化時にデフォルトメッセージを設定するようにしています。
@page "/route-parameter2/{message?}"
<h3>ルートパラメーターとルート制約2</h3>
<p>@Message</p>
@code {
[Parameter]
public string? Message { get; set; }
protected override void OnInitialized()
{
Message ??= "デフォルトメッセージ";
}
}
パラメーターを指定すると指定した値が画面に表示されます。
パラメーターを指定しなかった場合は、デフォルトで設定したメッセージが表示されます。
レンダリングフラグメント(RenderFragment)
最後に、レンダリングフラグメントの使い方を紹介します。
レンダリングフラグメントを使うと、コンポーネント呼び元で指定したコンテンツを子コンポーネント側に埋め込むことができます。
まずフラグメントを受け取る側の子コンポーネントです。
受け取る側は必ずRenderFragment型かつChildContentという名前のpublicプロパティを定義します。
<h3>子コンポーネントです</h3>
<p>親コンポーネントから渡されたコンテンツ:@ChildContent</p>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
子コンポーネントを呼び出す親コンポーネントです。
1つ目の子コンポーネント呼び出しは何も指定せずに、2つ目は子コンポーネントの開始タグと終了タグの間に渡したいコンテンツを設定しています。
@page "/render-fragment-parent"
<h2>親コンポーネントです</h2>
<RenderFragmentChild />
<RenderFragmentChild>
親コンポーネントで設定したコンテンツ。
<strong>タグを含んだ文字列</strong>なども渡すことができる
</RenderFragmentChild>
実行すると1つ目は空ですが、2つ目は親側で設定したコンテンツが子側で埋め込まれて表示されていることがわかります。