BlazorのRenderTreeBuilderを使用して動的に描画を行う
はじめに
Blazorについて勉強し始めて、「似たようなページがたくさんあるので、もっと抽象的にまとめたいなあ」と思ってた矢先、RenderTreeBuilderに出会いました。
もうなんでもアリ(誉め言葉)だな、と感じましたので、RenderTreeBuilderのご紹介です。
RenderTreeBuilderとは
RenderTreeBuilderは、BlazorでUIコンポーネントをレンダリングするために使用されるクラスで、コンポーネントのHTMLを動的に構築する際に利用されます。具体的には、RenderTreeBuilderを使って、コンポーネントがレンダリングする要素(タグやコンテンツ)を「ツリー」形式で構築します。
Blazorでは、コンポーネントのUIは、通常、Renderメソッド内でRenderTreeBuilderを使って構築されます。RenderTreeBuilderには、以下のような主なメソッドがあります:
- OpenElement: 新しいHTML要素を開く
- AddContent: 要素にテキストコンテンツを追加する
- CloseElement: 要素を閉じる
- AddAttribute: 要素に属性を追加する
- AddComponentParameter: コンポーネントのParameter属性変数に値を渡す
(AI検索より引用)
要するに、C#側でRazor構文を組み立てれますよ、という認識です。
環境
- .NET 8
- Blazor WASM
テストページ準備
以下のようにTestPage2とComponentTest2 , ComponentTest3を追加したBlazorサンプルプロジェクトがあるとします。
TestPage2はNavMenuに追加表示し、TestPage2を表示するとします。
TestPage2内では@rfを表示しているとします。
TestPage2
@*TestPage2.razor*@
@page "/mytest2"
<PageTitle>MyTest2</PageTitle>
<span>
@rf
</span>
// TestPage2.razor.cs
public partial class TestPage2
{
private RenderFragment? rf;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
}
}
次項以降でRenderFragment? rfを編集し、ページ内の表示を制御します。
動作確認
基本的な使い方
ComponentTest2をTestPage2からRenderTreeBuilderを使用して表示を行います。
以下のようにComponentTest2を用意します。ComponentTest2にはParameter属性のMsgとKVが存在し、ComponentTest2内で表示するとします。
ComponentTest2
@*ComponentTest2.razor*@
<h3>ComponentTest2</h3>
@*メッセージの変数を表示*@
<span>@Msg</span>
@*KeyValueペアを列挙*@
<ul>
@foreach (var o in KV)
{
<li>@o.Key: @o.Value</li>
}
</ul>
// ComponentTest2.razor.cs
public partial class ComponentTest2
{
[Parameter]
public string? Msg { get; set; }
[Parameter]
public Dictionary<string, object> KV { get; set; } = [];
}
TestPage2にて、RenderTreeBuilder.AddComponentParameterメソッドでComponentTest2の変数に値をセットします。
TestPage2
@*TestPage2.razor*@
@page "/mytest2"
<PageTitle>MyTest2</PageTitle>
<span>
@rf
</span>
// TestPage2.razor.cs
public partial class TestPage2
{
private RenderFragment? rf;
protected override async Task OnInitializedAsync()
{
// RenderTreeBuilderを使用して、コンポーネントを動的に生成する
rf = new RenderFragment(builder =>
{
int seq = 0;//コード行の番号
builder.OpenComponent<ComponentTest2>(seq++);
// Parameter属性の変数Msgに値をセットする
builder.AddComponentParameter(seq++, "Msg", "RenderTreeBuilder Test !");
// Parameter属性の変数KVに値をセットする
builder.AddComponentParameter(seq++, "KV", new Dictionary<string, object>
{
{ "Key1", "TestPage2Value1" },
{ "Key2", "TestPage2Value1" },
{ "Key3", "TestPage2Value1" },
{ "Key4", "TestPage2Value1" }
});
builder.CloseComponent();
});
await base.OnInitializedAsync();
}
}
実行結果:
RenderTreeBuilderで編集を行ったRenderFragmentが、TestPage2上のrfに表示されました。
応用的な使い方
ComponentTest2 , ComponentTest3をTestPage2からRenderTreeBuilderを使用して表示を行います。
以下のようにComponentTest2にRenderFragment? rf_comp2を準備し、さらにComponentTest2内でrf_comp2を表示するとします。
ComponentTest2
@*ComponentTest2.razor*@
<h3>ComponentTest2</h3>
@*メッセージの変数を表示*@
<span>@Msg</span>
@*KeyValueペアを列挙*@
<ul>
@foreach (var o in KV)
{
<li>@o.Key: @o.Value</li>
}
</ul>
@*パラメータで渡されたComponentを表示*@
<span>@rf_comp2</span>
// ComponentTest2.razor.cs
public partial class ComponentTest2
{
[Parameter]
public string? Msg { get; set; }
[Parameter]
public Dictionary<string, object> KV { get; set; } = [];
[Parameter]
public RenderFragment? rf_comp2 { get; set; }
}
さらにComponentTest3を準備します。ComponentTest3はMsgというParameter属性の変数を表示するのみとします。
ComponentTest3
@*ComponentTest3.razor*@
<h3>ComponentTest3</h3>
@*メッセージの変数を表示*@
<span>@Msg</span>
// ComponentTest3.razor.cs
public partial class ComponentTest3
{
[Parameter]
public string? Msg { get; set; }
}
TestPage2にて、RenderTreeBuilder.AddComponentParameterメソッドでComponentTest2の変数に値をセットします。
このときに、ComponentTest2のrf_comp2変数にRenderTreeBuilderを使用して、ComponentTest3もセットします。
TestPage2
@*TestPage2.razor*@
@page "/mytest2"
<PageTitle>MyTest2</PageTitle>
<span>
@rf
</span>
// TestPage2.razor.cs
public partial class TestPage2
{
private RenderFragment? rf;
protected override async Task OnInitializedAsync()
{
// RenderTreeBuilderを使用して、コンポーネントを動的に生成する
rf = new RenderFragment(builder =>
{
int seq = 0;//コード行の番号
builder.OpenComponent<ComponentTest2>(seq++);
// Parameter属性の変数Msgに値をセットする
builder.AddComponentParameter(seq++, "Msg", "RenderTreeBuilder Test !");
// Parameter属性の変数KVに値をセットする
builder.AddComponentParameter(seq++, "KV", new Dictionary<string, object>
{
{ "Key1", "TestPage2Value1" },
{ "Key2", "TestPage2Value1" },
{ "Key3", "TestPage2Value1" },
{ "Key4", "TestPage2Value1" }
});
// RenderTreeBuilderを使用して、rf_comp2用のRenderFragmentコンポーネントを動的に生成する
builder.AddAttribute(seq++, "rf_comp2", new RenderFragment(builder2 =>
{
int seq2 = 0;//コード行の番号
builder2.OpenComponent<ComponentTest3>(seq2++);
// Parameter属性の変数Msgに値をセットする
builder2.AddComponentParameter(seq2++, "Msg", "RenderTreeBuilder Test2 !");
builder2.CloseComponent();
}));
builder.CloseComponent();
});
await base.OnInitializedAsync();
}
}
実行結果:
RenderTreeBuilderで編集を行ったRenderFragmentが、TestPage2上のrfに表示されました。ComponentTest2上のrf_comp2もComponentTest3が指定され、表示されてます。
利用ケース
上記にも記載しましたが、1ページ内にコンポーネントの入れ子の入れ子、、とする場合、かつ最上位コンポーネントで下位を制御したい場合に私はよく使用します。
まとめ
Blazorでの簡単なRenderTreeBuilderの動きを確認しました。
Razor構文で記載することが正攻法ですが、ページの内の一部をより動的に表現したい場合などに便利です。
何かのお役に立てれば幸いです。
参考
- Microsoft Learn ASP.NET Core Blazor テンプレート コンポーネント
- Microsoft Learn ASP.NET Core Blazor の高度なシナリオ (レンダー ツリーの構築)
- Qiita パラメータで指定した HTML タグ要素をレンダリングする Razor コンポーネントを書く
- Blazor UIコンポーネントのタグ内に任意のHTMLを描画させる
Discussion