🕌

.NET 6 で動的にコンポーネントをレンダリングする機能が追加されたので試してみる

2021/10/11に公開

日付的に .NET 6 の正式リリース前の最後のリリースの RC2 が出てきても不思議じゃないですね!ということで引き続き .NET 6 の Blazor (WebAssembly しか自分は検証してません) の新機能を試してみたいと思います。

今日は、既に日本語ドキュメントページがある DynamicComponent について試してみようと思います。

https://docs.microsoft.com/ja-jp/aspnet/core/blazor/components/dynamiccomponent?view=aspnetcore-6.0

本文

ということでやっていきます。といってもこのコンポーネント自体は非常にシンプルで DynamicComponentType プロパティでレンダリングしたいコンポーネントの型を設定して Parameters プロパティで設定したいプロパティを IDictionary<string, object> で設定します。

全然ダイナミックじゃないですが以下のようにすることで Counter コンポーネントを DynamicComponent を使って表示できます。Blazor のプロジェクトを作って Index.razor に以下のように書いてみました。

Index.razor
@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

<DynamicComponent Type="typeof(Counter)" />

実行すると以下のように表示されます。Hello, world! の下に Counter コンポーネントが表示されます。

次にパラメーターを渡してみます。まずは Counter コンポーネントに InitialValue というパラメーターを追加して初期値を設定できるようにしてみました。

Counter.razor
@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [Parameter]
    public int InitialValue { get; set; }

    private int currentCount = 0;

    protected override void OnParametersSet()
    {
        base.OnParametersSet();
        currentCount = InitialValue;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

DynamicComponent の方で、この InitialValue プロパティを設定してみます。最初に書いたように Parameters プロパティに IDictionary<string, object> で渡します。なので渡すだけなら以下のように書けます。

Index.razor
@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

<DynamicComponent Type="typeof(Counter)" Parameters="@(new Dictionary<string, object> { ["InitialValue"] = 100 })" />

Parameters プロパティで直接 Dictionary<string, object> を生成して InitialValue に 100 を設定しています。実行すると以下のように表示された段階で既にカウンターの値が 100 になっています。

最後に Instance プロパティについて紹介します。これは DynamicComponent でレンダリングしている実際のコンポーネントのインスタンスを取得します。例えば Counter コンポーネントに値をリセットする Reset メソッドを追加して、それを DynamicComponent から呼んでみましょう。

まずは Reset メソッドを追加します。

Counter.razor
@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [Parameter]
    public int InitialValue { get; set; }

    private int currentCount = 0;

    // リセットメソッドを追加!
    public void Reset()
    {
        currentCount = InitialValue;
        StateHasChanged();
    }

    protected override void OnParametersSet()
    {
        base.OnParametersSet();
        currentCount = InitialValue;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

そして DynamicComponent@ref を使って変数に保存して、そこから Instance プロパティ経由で Counter のインスタンスを取得して Reset メソッドを呼び出しています。

Index.razor
@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

@* @ref で DynamicComponent をフィールドの変数に保持 *@
<DynamicComponent Type="typeof(Counter)" Parameters="@(new Dictionary<string, object> { ["InitialValue"] = 100 })" @ref="_counter" />


<button @onclick="ResetButton_Click">Reset</button>

@code {
    private DynamicComponent? _counter;

    private void ResetButton_Click()
    {
        // ボタンが押されたら DynamicComponent から Counter のインスタンスをとって Reset を呼ぶ
        if (_counter?.Instance is Counter c)
        {
            c.Reset();
        }
    }
}

実行すると以下のように動きます。Reset ボタンを押すと、ちゃんと Counter コンポーネントの Reset メソッドが呼ばれていることがわかると思います。

動的にしてみよう

さて、今まで DynamicComponent を不便なコンポーネントのように使ってきました!これまでの例では、そのまま Counter コンポーネントを置いた方がいいのでは…という感じだったのですが、本来は、データなどに応じて、その場に表示するコンポーネントを動的に切り替えるというのが意図された使い方です。

今までは、それを実行しようとすると以下のように自分で切り替わる可能性のある種類の数だけ if などで分岐する必要がありました。[1]

@foreach (var item in items)
{
    @* 表示に使うコンポーネントの種類を ComponentType というプロパティで列挙体で管理してる想定 *@
    @if (item.ComponentType == ComponentType.Foo)
    {
        <Foo Value="item.Value" />
    }
    else if (item.ComponentType == ComponentType.Bar)
    {
        <Bar Value="item.Value" />
    }
    else if (...)
    {
        @* 表示する可能性のあるコンポーネントの種類だけ続ける *@
    }
}

DynamicComponent を使うと表示するデータとあわせて、表示に使うコンポーネントの型を変数などで管理すればよくなります。例えば先ほどの例を DynamicComponent を使って書き換えると以下のような感じになるはずです。[1:1]

@* items の方に表示に使うコンポーネントの型を入れておく *@
var items = new[]
{
    new Item(Value = "value1", ComponentType = typeof(Foo)),
    new Item(Value = "value2", ComponentType = typeof(Bar)),
    new Item(Value = "value3", ComponentType = typeof(Baz)),
};

@* 表示は DynamicComponent でサクッと *@
@foreach (var item in items)
{
    <DynamicComponent Type="item.ComponentType" Parameters="@(new Dictionary<string, object> { ["Value"] = item.Value })" />
}

すっきりですね。

わからなかった点

軽く調べた感じだと DynamicComponentParameters プロパティ経由で EventCallback 型のパラメーターを渡す方法がわかりませんでした!単純にデリゲートを渡すと型変換に失敗するし EventCallback のインスタンス作るときのコンストラクタ引数はわからないし…。GitHub の Issue を見てみたけど特にイベントを渡す方法に関する言及は見つけることができませんでした。

探し方が悪いだけ、かもしれませんが…。

まとめ

ということで .NET 6 で追加される DynamicComponent を試してみました。イベントも設定がさくっとできたらいいんですけどね…。追々調べてみようと思います。

脚注
  1. 以下のコードは未実行なので間違いがあるかもしれません。イメージはこんな感じというものです ↩︎ ↩︎

Microsoft (有志)

Discussion