ASP.NET Core Blazor でパラメーターを上書きしないようにする

Note

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

作成者: Robert Haken

この記事では、再レンダリング時に Blazor アプリのパラメーターを上書きしないようにする方法について説明します。

上書きされたパラメーター

Blazor フレームワークでは、一般に安全な親から子へのパラメーターの割り当てが行われます。

  • パラメーターが予期せずに上書きされることはありません。
  • 副作用は最小限に抑えられます。 たとえば、追加のレンダリングは、無限のレンダリング ループが作成される可能性があるため、回避されます。

子コンポーネントは、親コンポーネントのレンダリング時に既存の値を上書きする可能性がある新しいパラメーター値を受け取ります。 子コンポーネントでパラメーター値が誤って上書きされることは、1 つまたは複数のデータバインド パラメーターを使用してコンポーネントを開発しており、開発者が子のパラメーターに直接書き込む場合に多く発生します。

  • 子コンポーネントは、親コンポーネントの 1 つまたは複数のパラメーター値を使用してレンダリングされます。
  • 子によって、パラメーターの値が直接書き込まれます。
  • 親コンポーネントがレンダリングされ、子のパラメーターの値が上書きされます。

パラメーター値の上書きの可能性は、子コンポーネントのプロパティ set アクセサーにも及びます。

重要

一般的なガイダンスとして、コンポーネントが初めてレンダリングされた後に、独自のパラメーターに直接書き込むコンポーネントを作成しないでください。

次が実行される ShowMoreExpander コンポーネントについて考えてみましょう。

  • タイトルをレンダリングします。
  • 選択した子コンテンツを表示します。
  • コンポーネント パラメーター (InitiallyExpanded) を使用して初期状態を設定できます。

次の ShowMoreExpander コンポーネントで上書きされるパラメーターを示した後に、このシナリオの正しい方法を示すために変更した ShowMoreExpander コンポーネントを紹介します。 説明されている動作を体験するために、次の例をローカルのサンプル アプリで使用することができます。

ShowMoreExpander.razor:

<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @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;
    }
}
<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @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;
    }
}
<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @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;
    }
}
<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @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;
    }
}
<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @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;
    }
}

ShowMoreExpander コンポーネントは、StateHasChanged を呼び出す可能性のある次の Expanders 親コンポーネントに追加されます。

Expanders.razor:

@page "/expanders"

<PageTitle>Expanders</PageTitle>

<h1>Expanders Example</h1>

<ShowMoreExpander InitiallyExpanded="false">
    Expander 1 content
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded="false" />

<button @onclick="StateHasChanged">Call StateHasChanged</button>
@page "/expanders"

<PageTitle>Expanders</PageTitle>

<h1>Expanders Example</h1>

<ShowMoreExpander InitiallyExpanded="false">
    Expander 1 content
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded="false" />

<button @onclick="StateHasChanged">Call StateHasChanged</button>
@page "/expanders"

<PageTitle>Expanders</PageTitle>

<h1>Expanders Example</h1>

<ShowMoreExpander InitiallyExpanded="false">
    Expander 1 content
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded="false" />

<button @onclick="StateHasChanged">Call StateHasChanged</button>
@page "/expanders"

<h1>Expanders Example</h1>

<ShowMoreExpander InitiallyExpanded="false">
    Expander 1 content
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded="false" />

<button @onclick="StateHasChanged">Call StateHasChanged</button>
@page "/expanders"

<h1>Expanders Example</h1>

<ShowMoreExpander InitiallyExpanded="false">
    Expander 1 content
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded="false" />

<button @onclick="StateHasChanged">Call StateHasChanged</button>

初期状態では、InitiallyExpanded プロパティが設定されると、ShowMoreExpander コンポーネントはそれぞれ独立して動作します。 子コンポーネントの状態は、想定どおりのままです。

親コンポーネント内で StateHasChanged が呼び出されると、それらのパラメーターが変更された可能性がある場合に、Blazor フレームワークは子コンポーネントを再レンダリングします。

  • Blazor が明示的に確認するパラメーター型のグループに対して、パラメーターのいずれかが変更されたことを Blazor が検出した場合、子コンポーネントを再レンダリングします。
  • 確認されていないパラメーター型に対しては、"パラメーターが変更されたかどうかに関係なく"、Blazor は子コンポーネントを再レンダリングします。 子コンテンツは、このカテゴリのパラメーター型に分類されます。子コンテンツは、他の変更可能なオブジェクトを参照するデリゲートである型 RenderFragment だからです。

Expanders コンポーネントの場合:

  • 1 つ目の ShowMoreExpander コンポーネントで、潜在的に変更可能な RenderFragment 内の子コンテンツを設定します。そのため、親コンポーネント内で StateHasChanged を呼び出すと、自動的にコンポーネントが再レンダリングされ、InitiallyExpanded の値がその初期値である false に上書きされる可能性があります。
  • 2 つ目の ShowMoreExpander コンポーネントでは、子コンテンツを設定しません。 そのため、潜在的に変更可能な RenderFragment は存在しません。 親コンポーネントで StateHasChanged を呼び出しても、子コンポーネントは自動的に再レンダリングされないので、コンポーネントの InitiallyExpanded の値は上書きされません。

前のシナリオでの状態を維持するには、ShowMoreExpander コンポーネントで "プライベート フィールド" を使用して、その状態を維持します。

次の変更された ShowMoreExpander コンポーネント:

  • 親から InitiallyExpanded コンポーネント パラメーター値を受け入れます。
  • コンポーネント パラメーター値を、OnInitialized イベントの "プライベート フィールド" (expanded) に割り当てます。
  • プライベート フィールドを使用して、その内部のトグル状態を維持します。これは、パラメーターに直接書き込まれないようにする方法を示しています。

Note

このセクションのアドバイスは、コンポーネント パラメーター set アクセサーの同様のロジックに及ぶため、同様の望ましくない副作用が発生する可能性があります。

ShowMoreExpander.razor:

<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @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;
    }
}
<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @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;
    }
}
<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @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;
    }
}
<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @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;
    }
}
<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @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;
    }
}

Note

変更された ShowMoreExpander には、初期化 (OnInitialized) 後の InitiallyExpanded パラメーターの変更は反映されません。 特定のシナリオでは、既に初期化されたコンポーネントで新しいパラメーター値が受け取られる場合があります。 これは、たとえば、同じコンポーネントを使用して異なる詳細ビューをレンダリングするマスター詳細ビューで、または /item/{id} ルート パラメーターが変更されて別の項目が表示される場合などに発生する可能性があります。

次の ToggleExpander コンポーネントについて考えてみましょう。

  • 内部と外部の両方から状態を変更できます。
  • 同じコンポーネント インスタンスが再利用された場合でも、新しいパラメーター値を処理します。

ToggleExpander.razor:

<div class="card bg-light mb-3" style="width:30rem">
    <div @onclick="Toggle" class="card-header">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

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

    [Parameter]
    public EventCallback<bool> ExpandedChanged { get; set; }

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

    private bool expanded;

    protected override void OnParametersSet()
    {
        expanded = Expanded;
    }

    private async void Toggle()
    {
        expanded = !expanded;
        await ExpandedChanged.InvokeAsync(expanded);
    }
}
<div class="card bg-light mb-3" style="width:30rem">
    <div @onclick="Toggle" class="card-header">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

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

    [Parameter]
    public EventCallback<bool> ExpandedChanged { get; set; }

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

    private bool expanded;

    protected override void OnParametersSet()
    {
        expanded = Expanded;
    }

    private async void Toggle()
    {
        expanded = !expanded;
        await ExpandedChanged.InvokeAsync(expanded);
    }
}
<div class="card bg-light mb-3" style="width:30rem">
    <div @onclick="Toggle" class="card-header">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

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

    [Parameter]
    public EventCallback<bool> ExpandedChanged { get; set; }

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

    private bool expanded;

    protected override void OnParametersSet()
    {
        expanded = Expanded;
    }

    private async void Toggle()
    {
        expanded = !expanded;
        await ExpandedChanged.InvokeAsync(expanded);
    }
}
<div class="card bg-light mb-3" style="width:30rem">
    <div @onclick="Toggle" class="card-header">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

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

    [Parameter]
    public EventCallback<bool> ExpandedChanged { get; set; }

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

    private bool expanded;

    protected override void OnParametersSet()
    {
        expanded = Expanded;
    }

    private async void Toggle()
    {
        expanded = !expanded;
        await ExpandedChanged.InvokeAsync(expanded);
    }
}
<div class="card bg-light mb-3" style="width:30rem">
    <div @onclick="Toggle" class="card-header">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

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

    [Parameter]
    public EventCallback<bool> ExpandedChanged { get; set; }

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

    private bool expanded;

    protected override void OnParametersSet()
    {
        expanded = Expanded;
    }

    private async void Toggle()
    {
        expanded = !expanded;
        await ExpandedChanged.InvokeAsync(expanded);
    }
}

ToggleExpander コンポーネントは、パラメーターの双方向同期を可能にする @bind-Expanded="{field}" バインド構文と共に使用する必要があります。

ExpandersToggle.razor:

@page "/expanders-toggle"

<PageTitle>Expanders Toggle</PageTitle>

<h1>Expanders Toggle</h1>

<ToggleExpander @bind-Expanded="expanded">
    Expander content
</ToggleExpander>

<button @onclick="Toggle">Toggle</button>

<button @onclick="StateHasChanged">Call StateHasChanged</button>

@code {
    private bool expanded;

    private void Toggle()
    {
        expanded = !expanded;
    }
}
@page "/expanders-toggle"

<PageTitle>Expanders Toggle</PageTitle>

<h1>Expanders Toggle</h1>

<ToggleExpander @bind-Expanded="expanded">
    Expander content
</ToggleExpander>

<button @onclick="Toggle">Toggle</button>

<button @onclick="StateHasChanged">Call StateHasChanged</button>

@code {
    private bool expanded;

    private void Toggle()
    {
        expanded = !expanded;
    }
}
@page "/expanders-toggle"

<PageTitle>Expanders Toggle</PageTitle>

<h1>Expanders Toggle</h1>

<ToggleExpander @bind-Expanded="expanded">
    Expander content
</ToggleExpander>

<button @onclick="Toggle">Toggle</button>

<button @onclick="StateHasChanged">Call StateHasChanged</button>

@code {
    private bool expanded;

    private void Toggle()
    {
        expanded = !expanded;
    }
}
@page "/expanders-toggle"

<h1>Expanders Toggle</h1>

<ToggleExpander @bind-Expanded="expanded">
    Expander content
</ToggleExpander>

<button @onclick="Toggle">Toggle</button>

<button @onclick="StateHasChanged">Call StateHasChanged</button>

@code {
    private bool expanded;

    private void Toggle()
    {
        expanded = !expanded;
    }
}
@page "/expanders-toggle"

<h1>Expanders Toggle</h1>

<ToggleExpander @bind-Expanded="expanded">
    Expander content
</ToggleExpander>

<button @onclick="Toggle">Toggle</button>

<button @onclick="StateHasChanged">Call StateHasChanged</button>

@code {
    private bool expanded;

    private void Toggle()
    {
        expanded = !expanded;
    }
}

親子バインディングの詳細については、以下のリソースを参照してください。

Blazor によって確認される正確な型の情報など、変更検出の詳細については、「ASP.NET Core Razor コンポーネントのレンダリング」を参照してください。