避免在 ASP.NET Core Blazor 中覆寫參數
注意
這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
由 Robert Haken 提供
本文說明如何避免在重新轉譯期間在 Blazor 應用程式中覆寫參數。
遭到覆寫的參數
Blazor 架構一般會強制指派安全的父對子參數:
- 參數不會意外遭到覆寫。
- 副作用會降到最低。 例如,會避免其他轉譯,因為其可能會產生無限循環的轉譯。
當父元件重新轉譯時,子元件會收到新的參數值,而可能覆寫現有的值。 如果使用一或多個與資料繫結的參數來開發元件,而且開發人員直接寫入到子系中的參數時,往往會意外覆寫子元件中的參數值:
- 子元件使用父元件的一或多個參數值進行轉譯。
- 子元件直接寫入到參數的值。
- 父元件重新轉譯,並覆寫子元件參數的值。
覆寫參數值的可能性也會延伸至子元件的屬性 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;
}
}
<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
元件會新增至下列 Expanders
父元件,後者可能會呼叫 StateHasChanged:
- 在開發人員程式碼中呼叫 StateHasChanged 會通知元件其狀態已有所變更,且通常會將元件重新轉譯加入佇列以更新 UI。 稍後在 ASP.NET Core Razor 元件生命週期和 ASP.NET Core Razor 元件轉譯中會詳細說明 StateHasChanged。
- 按鈕的
@onclick
指示詞屬性會將事件處理常式連結至按鈕的onclick
事件。 稍後在 ASP.NET Core Blazor 事件處理中會詳細說明事件處理。
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"
<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>
一開始,ShowMoreExpander
元件會在設定其 InitiallyExpanded
屬性時獨立運作。 子元件會維持其預期狀態。
如果在父元件中呼叫 StateHasChanged,且子元件的參數可能已變更,則 Blazor 架構會重新轉譯子元件:
- 針對 Blazor 明確檢查的參數類型群組,如果 Blazor 偵測到有任何參數發生變更,便會重新轉譯子元件。
- 針對未檢查的參數類型,「無論參數是否發生變更」,Blazor 都會重新轉譯子元件。 子內容會屬於這個類別的參數類型,因為子內容的類型為 RenderFragment,而這是會參考其他可變動物件的委派。
針對 Expanders
元件:
- 第一個
ShowMoreExpander
元件會在可能變動的 RenderFragment 中設定子內容,因此如果對父元件中的 StateHasChanged 進行呼叫,便會自動重新轉譯元件,並可能會將InitiallyExpanded
的值覆寫為其初始值false
。 - 第二個
ShowMoreExpander
元件不會設定子內容。 因此,可能變動的 RenderFragment 不存在。 如果對父元件中的 StateHasChanged 進行呼叫,將不會自動重新轉譯子元件,因此不會覆寫元件的InitiallyExpanded
值。
若要在上述案例中維持狀態,請使用 ShowMoreExpander
元件中的「私人欄位」來維持其狀態。
下列經過修訂的 ShowMoreExpander
元件:
- 會接受父代的
InitiallyExpanded
元件參數值。 - 會將元件參數值指派給
OnInitialized
事件中的「私人欄位」(expanded
)。 - 會使用私人欄位來維持其內部的切換狀態,以示範如何避免直接寫入到參數中。
注意
本節中的建議可延伸至元件參數 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;
}
}
<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;
}
}
注意
修改後的 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 Task 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 Task 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 Task 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 Task 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 Task 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 Task 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"
<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 元件轉譯。