Blazorコンポーネントのパラメータとカスケーディング値

パラメータ

一、コンポーネントパラメータ

コンポーネントを使用する際、パラメータを介してデータをコンポーネントに渡すことができます。データを受け取るコンポーネントは、[Parameter]属性を使用して対応するパブリックプロパティを定義する必要があります。以下の例では、組み込み参照型(System.String)とユーザー定義の参照型(PanelContent)がコンポーネントパラメータとして渡されています。

PanelContent.cs

namespace BlazorSample;

public class PanelContent
{
    public string? Text { get; set; }
    public string? Style { get; set; }
}

ParameterChild.razor

<div class="card w-25" style="margin-bottom:15px">
    <div class="card-header font-weight-bold">@Header</div>
    <div class="card-body" style="font-style:@Content.Style">
        @Content.Text
    </div>
</div>

@code {
    [Parameter]
    public string Header { get; set; } = "子コンポーネントによる設定";

    [Parameter]
    public PanelContent Content { get; set; } =
        new()
        {
            Text = "子コンポーネントによる設定。",
            Style = "normal"
        };
}

Parameter.razor

@page "/parameter"

<PageTitle>パラメータ</PageTitle>

<h1>パラメータの例</h1>

<h1>子コンポーネント(属性値なし)</h1>

<ParameterChild/>

<h1>子コンポーネント(属性値あり)</h1>

<ParameterChild Header="親コンポーネントによる設定"
                Content="@(new PanelContent() { Text = "親コンポーネントによる設定。", Style = "italic" })" />

コンポーネントパラメータ使用時の推奨事項

  • 常に引用符を使用する:HTML5の仕様によれば、パラメータ属性値の引用符はオプションです。例えば、Value=thisValue="this"の両方がサポートされますが、常に引用符を使用することを推奨します。
  • Razor式以外のテキストでは、常に@を避ける。例:IsFixed="true"MyComponent="this"MyComponent="null"など。
  • コンポーネントパラメータを定義する際は、自動プロパティとして宣言してください。getまたはsetアクセサーにカスタムロジックを配置しないでください。コンポーネントパラメータは親コンポーネントから子コンポーネントへ情報を伝達する専用のチャネルとして設計されています。子コンポーネントのプロパティのsetアクセサーに親コンポーネントの再レンダリングを引き起こすロジックが含まれている場合、無限のレンダリングループが発生します。受信したパラメータ値を変換する必要がある場合は、パラメータプロパティに基づいて変換されたデータを提供する別のプロパティまたはメソッドを作成することをお勧めします。

非同期式はサポートされていません コンポーネントをレンダリングする際、BlazorはRazor式で非同期の処理を実行できません。例:<ParameterChild Title="@await ..." />。これはBlazorが対話型UIのレンダリングを目的としているためです。対話型UIでは、常に何らかのコンテンツが表示される必要があるため、レンダリングフローをブロックすることは意味がありません。代わりに、非同期の処理は非同期ライフサイクルイベント期間中に実行されます。各非同期ライフサイクルイベントの後、コンポーネントは再度レンダリングされる可能性があります。 パラメータ値を非同期で取得するには、コンポーネントはOnInitializedAsyncライフサイクルイベントを使用して実装できます。

<ParameterChild Header="@header" />

@code {
  private string? header;
  
  protected override async Task OnInitializedAsync()
  {
      header = await ...;
  }
}

混合代入はサポートされていません Blazorでは、テキストと式結果を連結してパラメータに割り当てることはサポートされていません。例:<ParameterChild Header="設定者: @(panelData.Header)"/>。 混合代入を使用するには、メソッド、追加のフィールド、またはプロパティを使用して実装してください。

[EditorRequired]属性 コンポーネントパラメータプロパティを定義する際、[EditorRequired]属性を使用してそのプロパティにパラメータ値を提供する必要があることを指定できます。そうしないと、コンパイラまたはビルドツールが警告を表示する可能性があります。(これは警告のみであり、ブラウザではアクセス可能です)この属性は[Parameter]属性でマークされたプロパティでのみ有効です。

RequiredTest.razor

<h3>@Header</h3>

@code {
    [Parameter]
    [EditorRequired]
    public string? Header { get; set; } = "デフォルトヘッダー";
}

RequiredComponent.razor

@page "/required"
@rendermode InteractiveServer

<h1>必須テスト</h1>

<RequiredTest Header="パラメータ値を提供すると警告は表示されません"/>

タプルのサポート Blazorでは、コンポーネントパラメータとRenderFragmentタイプはタプルをサポートしています。

RenderTupleChild.razor

<div class="card w-50" style="margin-bottom:15px">
    <div class="card-header font-weight-bold">タプルカード</div>
    <div class="card-body">
        - 整数: @Data?.Item1
- 文字列: @Data?.Item2
- 真偽値: @Data?.Item3
    </div>
</div>

@code {
    [Parameter]
    public (int, string, bool)? Data { get; set; }
}

RenderTupleParent.razor

@page "/render-tuple-parent"

<PageTitle>タプル親コンポーネント</PageTitle>

<h1>タプル親コンポーネントの例</h1>

<RenderTupleChild Data="data" />

@code {
    private (int, string, bool) data = new(999, "私は不正行為を目指しています。", true);
}

名前付きタプルの使用も可能です。

NamedTupleChild.razor

<div class="card w-50" style="margin-bottom:15px">
    <div class="card-header font-weight-bold">タプルカード</div>
    <div class="card-body">
        - 整数: @Data?.IntegerValue
- 文字列: @Data?.StringValue
- 真偽値: @Data?.BooleanValue
    </div>
</div>

@code {
    [Parameter]
    public (int IntegerValue, string StringValue, bool BooleanValue)? Data { get; set; }
}

NamedTuples.razor

@page "/named-tuples"

<PageTitle>名前付きタプル</PageTitle>

<h1>名前付きタプルの例</h1>

<NamedTupleChild Data="data" />

@code {
    private (int IntegerValue, string StringValue, bool BooleanValue) data = 
        new(999, "私は不正行為を目指しています。", true);
}

null値のサポート デフォルトでは、Blazorコンポーネントパラメータはnull値の受け取りを許可していません。null値を渡すと、コンパイル時または実行時エラーが発生します。null値の受け取りを許可するには、コンポーネントパラメータを定義する際にAllowNull属性を使用する必要があります。

[Parameter, AllowNull]
public string NullableParameter { get; set; }

二、ルートパラメータ

[Parameter]属性を使用してパブリックプロパティを定義することで、ルートパラメータを受け取ることもできます。

@page "/route-parameter/{text}"

<p>Blazorは@Textです!</p>

@code {
    [Parameter]
    public string? Text { get; set; }
}

三、レンダーフラグメント

1、子コンテンツレンダーフラグメント

コンポーネント内では、RenderFragmentタイプを使用してChildContentという名前のコンポーネントパラメータを定義し、コンポーネントの子コンテンツを受け取ることができます。ChildContentパラメータを使用すると、コンポーネント内部に子コンテンツをレンダリングする領域を定義でき、コンポーネントをより柔軟にし、必要に応じて異なるコンテンツを動的にレンダリングできます。

  • デフォルトでは、コンポーネントは直接の子コンテンツを自動的にChildContentという名前のレンダーフラグメントプロパティにカプセル化します
  • RenderFragmentはイベントコールバックをサポートしません
  • RenderFragmentTest.razor
<h3>子コンテンツフラグメントテスト</h3>

<div>
    @ChildContent
</div>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}
  • RenderFragmentPanel.razor
@page "/render-fragment"

<h3>レンダーフラグメントパネル</h3>

<RenderFragmentTest>
    <h2>子コンテンツ</h2>
</RenderFragmentTest>
@*等価*@
<RenderFragmentTest>
		<ChildContent>
		    <h2>子コンテンツ</h2>
    </ChildContent>
</RenderFragmentTest>

複数の子コンテンツレンダーフラグメントがある場合は、異なるプロパティ名を定義して区別できます。

  • RenderTest.razor
<h3>レンダーテスト</h3>

@{
    @:最初の子フラグメント
    @FirstChild
    <br/>
    @:2番目の子フラグメント
    @SecondChild
    <br/>
}

@code {
    [Parameter]
    public RenderFragment? FirstChild { get; set; }

    [Parameter]
    public RenderFragment? SecondChild { get; set; }
}
  • Home.razor
@page "/"

<PageTitle>ホーム</PageTitle>
<h1>こんにちは、世界!</h1>
あなたの新しいアプリへようこそ。

<RenderTest>
    <FirstChild>
        最初のコンテンツ
    </FirstChild>
    <SecondChild>
        2番目のコンテンツ
    </SecondChild>
</RenderTest>

@code {
    private void ErrorHappen()
    {
        throw new Exception("OK");
    }
}

2、再利用可能なレンダーフラグメント

子コンテンツレンダーフラグメントとして使用するだけでなく、RenderFragmentを使用して汎用のレンダーフラグメントをカスタマイズすることもできます。この場合、レンダーフラグメントはコンポーネントパラメータとして定義する必要はなく、プロパティ名をChildContentとする必要もありません。

@RenderWelcomeInfo

<p>ウェルカム情報を2回目にレンダリング:</p>

@RenderWelcomeInfo

@code {
    private RenderFragment RenderWelcomeInfo =  @<p>新しいアプリへようこそ!</p>;
}

レンダーフラグメントパラメータ

RenderFragment<TValue>を使用してレンダーフラグメントにパラメータを渡すことができます。

@page "/razor-template"

<PageTitle>Razorテンプレート</PageTitle>

<h1>Razorテンプレートの例</h1>

@timeTemplate

@petTemplate(new Pet { Name = "ナッティ・レックス" })

@code {
    private RenderFragment timeTemplate = @<p>時刻は@DateTime.Nowです。</p>;
    private RenderFragment<Pet> petTemplate = (pet) => @<p>ペット: @pet.Name</p>;

    private class Pet
    {
        public string? Name { get; set; }
    }
}

3、テンプレートコンポーネント

テンプレートコンポーネントとは、子コンポーネント内で1つ以上のRenderFragmentまたはRenderFragment<TValue>を使用してレンダーフラグメントコンポーネントパラメータを定義することです。その後、親コンポーネントが子コンポーネントを使用する際には、子コンポーネントの子コンテンツ内に対応するレンダーフラグメントを設定して、具体的なレンダリングコンテンツを子コンポーネントに渡します。子コンポーネントはテンプレートとしてのみ機能し、実際のレンダリングコンテンツは親コンポーネントの設定に基づいてレンダーフラグメントによって変更されます。

テンプレートコンポーネントの定義

以下の例では、TableTemplateコンポーネントでTableHeaderとRowTemplateという2つのレンダーフラグメントをコンポーネントパラメータとして定義しています。使用時には、TableTemplateコンポーネントの子コンテンツ内にTableHeaderとRowTemplateを渡すことができます。

  • TableTemplate.razor
@typeparam TItem
@using System.Diagnostics.CodeAnalysis

|
||
|

@code {
    [Parameter]
    public RenderFragment? TableHeader { get; set; }

    [Parameter]
    public RenderFragment<TItem>? RowTemplate { get; set; }

    [Parameter, AllowNull]
    public IReadOnlyList<TItem> Items { get; set; }
}

テンプレートコンポーネントのリストパラメータの子項目名の指定

子コンポーネントのリスト項目の子項目名を定義するには、コンポーネント要素のContextプロパティを使用できます。

  • Pets1.razor
@page "/pets-1"

<PageTitle>ペット1</PageTitle>

<h1>ペットの例1</h1>

<TableTemplate Items="pets" Context="pet">
    <TableHeader>
        <th>ID</th>
        <th>名前</th>
    </TableHeader>
    <RowTemplate>
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>

@code {
    private List<Pet> pets = new()
    {
        new Pet { PetId = 2, Name = "ミスター・ビッグルスワース" },
        new Pet { PetId = 4, Name = "セイレム・セイバーハーゲン" },
        new Pet { PetId = 7, Name = "K-9" }
    };

    private class Pet
    {
        public int PetId { get; set; }
        public string? Name { get; set; }
    }
}

レンダーフラグメントの子項目名の指定

テンプレートコンポーネントの特定のレンダーフラグメントに子項目名を個別に設定することもできます。これにもContextプロパティを使用します。

  • Pets2.razor
@page "/pets-2"

<PageTitle>ペット2</PageTitle>

<h1>ペットの例2</h1>

<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>名前</th>
    </TableHeader>
    <RowTemplate Context="pet">
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>

@code {
    private List<Pet> pets = new()
    {
        new Pet { PetId = 2, Name = "ミスター・ビッグルスワース" },
        new Pet { PetId = 4, Name = "セイレム・セイバーハーゲン" },
        new Pet { PetId = 7, Name = "K-9" }
    };

    private class Pet
    {
        public int PetId { get; set; }
        public string? Name { get; set; }
    }
}

暗的パラメータの直接使用

RenderFragment<TValue>タイプのコンポーネントパラメータはcontextという名前の暗的パラメータを持ち、このパラメータを子項目オブジェクトとして直接使用できます。

  • 注意点:RenderFragment<TValue>のみがcontext暗的パラメータを持ちます。RenderFragmentは持ちません。また、テンプレートコンポーネントにContextプロパティが設定されている場合、その下のいかなるレンダーフラグメントでも@contextを使用できません。特定のレンダーフラグメントにContext名が定義されている場合、そのレンダーフラグメント内では@contextを使用できません。
  • Pets3.razor
@page "/pets-3"

<PageTitle>ペット3</PageTitle>

<h1>ペットの例3</h1>

<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>名前</th>
    </TableHeader>
    <RowTemplate>
        <td>@context.PetId</td>
        <td>@context.Name</td>
    </RowTemplate>
</TableTemplate>

@code {
    private List<Pet> pets = new()
    {
        new Pet { PetId = 2, Name = "ミスター・ビッグルスワース" },
        new Pet { PetId = 4, Name = "セイレム・セイバーハーゲン" },
        new Pet { PetId = 7, Name = "K-9" }
    };

    private class Pet
    {
        public int PetId { get; set; }
        public string? Name { get; set; }
    }
}

四、コンポーネントパラメータのジェネリックサポート

1、通常の使用法

Blazorコンポーネントでコンポーネントパラメータにジェネリックを使用するには、コンポーネントのトップでRazorの@typeparamディレクティブを使用してジェネリック型パラメータを宣言し、その後でコンポーネントパラメータを定義する必要があります。

TypeParamTest.razor

@typeparam T

<h3>ジェネリックパラメータテスト</h3>

@foreach (var t in TList!)
{
    <div>@t</div>
}

@code {
    [Parameter]
    public List<T>? TList { get; set; }
}

TypeParamPanel.razor

@page "/type-param"

<TypeParamTest T="string" TList="@(new List<string>{"a", "b", "c"})"/>
<TypeParamTest T="int" TList="@(new List<int>{1, 2, 3})"/>

自動型推論 実際には、コンポーネントを使用する際にジェネリックコンポーネントパラメータに設定する値が明確な型を持つ場合、ジェネリック型を明示的に設定する必要はなく、C#が自動的にジェネリック型推論を行います。つまり、以下のように簡略化できます。

TypeParamPanel.razor

@page "/type-param"

<TypeParamTest TList="@(new List<string>{"a", "b", "c"})"/>
<TypeParamTest TList="@(new List<int>{1, 2, 3})"/>

2、カスケーディングジェネリック型の伝達

カスケーディングジェネリック型の伝達とは、簡単に言えば親コンポーネントで子コンポーネントのジェネリックコンポーネントパラメータのジェネリック型を設定することです。これを実現するには、親コンポーネントのトップで@attribute [CascadingTypeParameter(nameof(T))]@typeparam Tを組み合わせて使用する必要があります。

TypeParamTest.razor

@typeparam T

<h3>ジェネリックパラメータテスト</h3>

@foreach (var t in TList!)
{
    <div>@t</div>
}

@code {
    [Parameter]
    public List<T>? TList { get; set; }
}

CascadingTypeParameterTest.razor

@attribute [CascadingTypeParameter(nameof(T))]
@typeparam T

@ChildContent

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

CascadingTypeParameterPanel.razor

@page "/cascading-type-parameter"

<h3>カスケーディングジェネリックパラメータ型伝達テスト</h3>

<CascadingTypeParameterTest T="string">
    <TypeParamTest TList="@StrList"/>
</CascadingTypeParameterTest>

<CascadingTypeParameterTest T="int">
    <TypeParamTest TList="@IntList" />
</CascadingTypeParameterTest>

@code {
    private List<string> StrList = new List<string> { "A","B","C" };
    private List<int> IntList = new List<int> { 1, 2, 3 };
}

組み込みコンポーネントによるジェネリックカスケーディング値の伝達 実際にはカスケーディング値を伝達しますが、このカスケーディング値はジェネリックパラメータが設定されています。カスケーディング値の具体的な使用については、後述で詳しく説明します。

TypeParamTest.razor

@typeparam T

<h3>ジェネリックパラメータテスト</h3>

@foreach (var t in TList!)
{
    <div>@t</div>
}

@code {
    [CascadingParameter]
    public List<T>? TList { get; set; }
}

CascadingTypeParameterTest.razor

@attribute [CascadingTypeParameter(nameof(T))]
@typeparam T

@ChildContent

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

CascadingTypeParameterPanel.razor

@page "/cascading-type-parameter"

<h3>カスケーディングジェネリックパラメータ型伝達テスト</h3>

<CascadingValue Value="@StrList">
    <CascadingTypeParameterTest T="string">
        <TypeParamTest/>
    </CascadingTypeParameterTest>
</CascadingValue>

<CascadingValue Value="@IntList">
    <TypeParamTest T="int"/>
</CascadingValue>

@code {
    private List<string> StrList = new List<string> { "A","B","C" };
    private List<int> IntList = new List<int> { 1, 2, 3 };
}

五、パラメータの上書きを避ける

以下の例で、親コンポーネントでStateHasChanged()を呼び出した場合を観察してください:

最初のShowMoreExpanderコンポーネントは子コンテンツが設定されているため、親コンポーネントでStateHasChanged()を呼び出すと自動的に再レンダリングされ、その際にInitiallyExpandedの値が初期値falseに上書きされます。

2番目のShowMoreExpanderコンポーネントはコンポーネントパラメータのみが設定されており、親コンポーネントで設定されたコンポーネントパラメータ値が変化していないため、親コンポーネントでStateHasChanged()を呼び出しても再レンダリングされません。

ShowMoreExpander.razor

<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">詳細表示 (展開 = @InitiallyExpanded)</h2>
    </div>
    @if (InitiallyExpanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private void ShowMore()
    {
        InitiallyExpanded = true;
    }
}

Expanders.razor

@page "/expanders"

<PageTitle>エキスパンダー</PageTitle>

<h1>エキスパンダーの例</h1>

<ShowMoreExpander InitiallyExpanded=false>
    エキスパンダー1のコンテンツ
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded=false/>

<button @onclick="StateHasChanged">StateHasChangedを呼び出す</button>

上記の例のように、親コンポーネントでStateHasChanged()を呼び出したときに子コンポーネントの再レンダリングが発生すると、子コンポーネント内部で変更されたコンポーネントパラメータが上書きされてしまいます。これを避けるには、コンポーネントでプライベートフィールドを使用して状態を保持し、子コンポーネントを以下のように変更します:

ShowMoreExpander.razor

<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">詳細表示 (展開 = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    private bool expanded;

    [Parameter]
    public bool InitiallyExpanded { get; set; }
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    protected override void OnInitialized()
    {
        expanded = InitiallyExpanded;
    }

    private void Expand()
    {
        expanded = true;
    }
}

六、属性スプラッティングと任意のパラメータ

1、属性スプラッティング

Razorコンポーネントで要素の属性を設定する際、多くの属性を設定する必要があることがよくあります。Razorコンポーネントはこれらの属性をすべてDictionary<string, object>オブジェクトに配置し、要素上でRazorの属性ディレクティブ@attributesを使用して展開することをサポートしています。

以下の例では、2つのInput要素は属性設定の点で同じ効果があります。

@page "/splat"

<PageTitle>スプラット!</PageTitle>

<h1>スプラットパラメータの例</h1>

<input maxlength="@maxlength"
       placeholder="@placeholder"
       required="@required"
       size="@size" />

<input @attributes="InputAttributes" />

@code {
    private string maxlength = "10";
    private string placeholder = "入力プレースホルダーテキスト";
    private string required = "required";
    private string size = "50";

    private Dictionary<string, object> InputAttributes { get; set; } = new()
    {
        { "maxlength", "10" },
        { "placeholder", "入力プレースホルダーテキスト" },
        { "required", "required" },
        { "size", "50" }
    };
}

要素で@attributesを使用する際は、その配置位置に注意してください。属性を展開する際は右から左に行われ、つまり右側が左側よりも優先されます。そのため、同じ名前の属性を@attributesの右側に配置すると、右側の属性が優先的に使用されます。以下の例を参照してください:

@page "/splat"

<PageTitle>スプラット!</PageTitle>

<h1>スプラットパラメータの例</h1>

<input @attributes="InputAttributes" placeholder="右側のフォント"/>

@code {
    private Dictionary<string, object> InputAttributes { get; set; } = new()
    {
        { "maxlength", "10" },
        { "placeholder", "入力プレースホルダーテキスト" },
        { "required", "required" },
        { "size", "50" }
    };
}

2、任意のパラメータ

渡されたコンポーネントパラメータが多すぎる場合、コンポーネントパラメータを定義する際に[Parameter]属性でCaptureUnmatchedValues=trueを設定すると、他のいかなるコンポーネントパラメータにも一致しないすべての渡されたパラメータ値を受け入れることができます。

  • コンポーネント内では、CaptureUnmatchedValuesを使用してコンポーネントパラメータを1つだけ定義できます
  • CaptureUnmatchedValuesと一緒に使用される属性タイプは、キー値ストレージのコレクション(例:Dictionary<string, object>IEnumerable<KeyValuePair<string, object>>、またはIReadOnlyDictionary<string, object>)である必要があります。

AttributeOrderChild.razor

<input @attributes="AdditionalAttributes"/>

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public IDictionary<string, object>? AdditionalAttributes { get; set; }
}

AttributeOrder.razor

@page "/attribute-order"

<PageTitle>属性順序</PageTitle>

<h1>属性順序の例</h1>

<AttributeOrderChild maxlength="10" placeholder="入力プレースホルダーテキスト" required="required" size="50"/>

カスケーディング値

Blazorでは、カスケーディング値を使用して上位コンポーネントから下位コンポーネントにデータを渡すことができます。

一、ルートレベルカスケーディング値の登録

コンポーネント階層全体に対してルートレベルのカスケーディング値を登録し、更新通知とサブスクライブに対応した名前付きカスケーディング値をサポートできます。

固定のルートレベルカスケーディング値

ルートレベルのカスケーディング値を登録するには、Program.csIServiceCollectionオブジェクトの拡張メソッドAddCascadingValueを使用します。

  • IServiceCollection AddCascadingValue<TValue>(Func<IServiceProvider, TValue> initialValueFactory):指定されたインスタンスオブジェクトを固定のカスケーディング値として登録します。
  • IServiceCollection AddCascadingValue<TValue>(string name, Func<IServiceProvider, TValue> initialValueFactory):指定されたインスタンスオブジェクトを名前付きのカスケーディング値として登録します。
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });

注意点:コンポーネントタイプをルートレベルのカスケーディング値として登録しても、そのタイプのための他のサービスを登録したり、コンポーネント内でサービスをアクティブ化したりすることはできないため、AddCascadingValueを使用してコンポーネントタイプをカスケーディング値として登録することは避けるべきです。代わりにCascadingValueコンポーネントを使用してください。

動的なルートレベルカスケーディング値

デフォルトでは、カスケーディング値は更新通知をサポートしており、カスケーディング値が変更されると自動的にNotifyChangedAsyncが呼び出されます。

  • 注意点:通常のコンポーネントパラメータと同様に、カスケーディング値が変更されると、カスケーディング値を受け取るコンポーネントが再レンダリングされるため、サブスクリプションはオーバーヘッドを発生させパフォーマンスを低下させます。そのため、値が変更されない場合はisFixedtrueに設定してください。デフォルトはfalseです。
builder.Services.AddCascadingValue(sp =>
{
    var dalek = new Dalek { Units = 789 };
    var source = new CascadingValueSource<Dalek>(dalek, true);
    return source;
});

二、[CascadingParameter]属性

下位コンポーネントでカスケーディング値を使用するには、[CascadingParameter]属性を使用してプロパティを宣言する必要があります。宣言後、そのプロパティは指定されたカスケーディング値をプロパティ値として受け取ります。

  • 注意点:カスケーディングパラメータはレンダリングモード間でデータを渡しません。例えば、Blazor Web App Autoプロジェクトでサーバー側のみにカスケーディング値を定義した場合、WebAssemblyフェーズではこのカスケーディング値が見つからず、その逆も同様です。
@code {
    // プロパティタイプに基づいてカスケーディング値を検索
    [CascadingParameter]
    public Dalek? Dalek { get; set; }
    
    // 指定された名前でカスケーディング値を検索
    [CascadingParameter(Name = "AlphaGroup")]
    public Dalek? AlphaGroupDalek { get; set; }
}

三、CascadingValueコンポーネント

CascadingValue:カスケーディング値伝達コンポーネント。Valueプロパティを設定することで、下位コンポーネントに単一のカスケーディング値を渡すことができます。

  • ValueCascadingValueコンポーネントのプロパティ。カスケーディング値を設定するために使用します。
  • NameCascadingValueコンポーネントのプロパティ。カスケーディング値の名前を設定するために使用します。設定しないこともできます。デフォルトは受信変数の変数名です。
  • IsFixedCascadingValueコンポーネントのプロパティ。デフォルトはfalseです。trueの場合、カスケーディング値は固定されており、更新通知を行う必要がないことを示します。

ここでは、カスタムのテーマ情報タイプThemeInfoインスタンスを渡す例を挙げます。

ThemeInfo.cs

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}

レベルカスケーディング値 レイアウトコンポーネントMainLayout.razorで、テーマコンテンツをCascadingValueコンポーネント内に含め、Valueプロパティを設定してカスケーディング値を渡すことができます。

MainLayout.razor

......
        <CascadingValue Value="@theme">
            <article class="content px-4">
                @Body
            </article>
        </CascadingValue>
......

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}

次に、このカスケーディング値を使用するコンポーネントでCascadingParameter属性を使用して宣言します。

CascadingValueTest.razor

@page "/cvt"
@rendermode InteractiveWebAssembly

<h1>@theme?.ButtonClass</h1>

@code {
    [CascadingParameter]
    public ThemeInfo? theme { get; set; }
}

グローバルカスケーディング値 Blazor Webは、アプリケーション全体に適用されるカスケーディング値設定方法を提供しています。Routesコンポーネントで、RouterコンポーネントをCascadingValueコンポーネントでラップする方法です。

Routes.razor

<CascadingValue Value="@theme">
    <Router ...>
        ...
    </Router>
</CascadingValue>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}

注意点:App.razorコンポーネント内で、Routes.razorコンポーネントをCascadingValueコンポーネントでラップすることはサポートされていません。

複数のカスケーディング値の伝達 下位コンポーネントに複数のカスケーディング値を同時に渡すには、CascadingValueをネストし、カスケーディング値の名前を指定する方法を使用します。

Routes.razor

<CascadingValue Value="@a" Name="Value1">
    <CascadingValue Value="@b" Name="Value2">
        <Router ......>
            ......
        </Router>
    </CascadingValue>
</CascadingValue>

@code {
    private int a = 1;
    private int b = 2;
}

コンポーネント間でのカスケーディング値の伝達 コンポーネント間でのカスケーディング値の伝達とは、ここでは子コンポーネントから親コンポーネントへの伝達を意味します。これはCascadingValueコンポーネントの巧妙な使用法であり、その核心は親コンポーネントがRenderFragmentを使用して子コンテンツを取得し、コンポーネント内でCascadingValueコンポーネントを使用してそれを子コンテンツに渡すという点です。子コンポーネントは[CascadingParameter]を使用して親コンポーネントオブジェクトを取得した後、渡したいものを渡します。

  • 注意点:ここでは親コンポーネントのすべての子コンポーネントをレンダリングコンテンツとして扱うため、RenderFragmentの使用法は固定されています。[Parameter]属性を使用する必要があり、変数名は必ずChildContentである必要があります

以下は公式が提供する例で、TabSetコンポーネントがTabの親コンポーネントであり、複数のTabコンポーネントを管理します。

TabSet.razor

@using BlazorSample.UIInterfaces

<!-- タブヘッダーを表示 -->

<CascadingValue Value="this">
     @ChildContent 
</CascadingValue>

<!-- アクティブなタブのみにボディを表示 -->

<div class="nav-tabs-body p-4">
    @ActiveTab?.ChildContent
</div>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public ITab? ActiveTab { get; private set; }

    public void AddTab(ITab tab)
    {
        if (ActiveTab is null)
        {
            SetActiveTab(tab);
        }
    }

    public void SetActiveTab(ITab tab)
    {
        if (ActiveTab != tab)
        {
            ActiveTab = tab;
            StateHasChanged();
        }
    }
}

Tab.razor

@using BlazorSample.UIInterfaces
@implements ITab

<li>
    <a @onclick="ActivateTab" class="nav-link @TitleCssClass" role="button">
        @Title
    </a>
</li>

@code {
    [CascadingParameter]
    public TabSet? ContainerTabSet { get; set; }

    [Parameter]
    public string? Title { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private string? TitleCssClass => 
        ContainerTabSet?.ActiveTab == this ? "active" : null;

    protected override void OnInitialized()
    {
        ContainerTabSet?.AddTab(this);
    }

    private void ActivateTab()
    {
        ContainerTabSet?.SetActiveTab(this);
    }
}

ExampleTabSet.razor

@page "/example-tab-set"

<TabSet>
    <Tab Title="最初のタブ">
        <h4>最初のタブからの挨拶!</h4>

        <label>
            <input type="checkbox" @bind="showThirdTab" />
            3番目のタブを切り替え
        </label>
    </Tab>

    <Tab Title="2番目のタブ">
        <h4>2番目のタブからのこんにちは!</h4>
    </Tab>

    @if (showThirdTab)
    {
        <Tab Title="3番目のタブ">
            <h4>消える3番目のタブへようこそ!</h4>
            <p>このタブは最初のタブから切り替えます。</p>
        </Tab>
    }
</TabSet>

@code {
    private bool showThirdTab;
}

タグ: Blazor コンポーネントパラメータ カスケーディング値 レンダーフラグメント ジェネリック

8月1日 03:35 投稿