Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.
Advertência
Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.
Por Robert Haken
Este artigo explica como evitar sobrescrever parâmetros nas Blazor aplicações durante a rerenderização.
Parâmetros sobrescritos
O Blazor quadro geralmente impõe uma atribuição segura de parâmetros de pai para filho:
- Os parâmetros não são inesperadamente substituídos.
- Os efeitos secundários são minimizados. Por exemplo, são evitadas renderizações adicionais porque podem criar ciclos de renderização infinitos.
Um componente filho recebe novos valores de parâmetros que podem sobrescrever valores existentes quando o componente pai é renderizado novamente. A sobrescrição acidental dos valores dos parâmetros num componente filho ocorre frequentemente quando o componente é desenvolvido com um ou mais parâmetros ligados a dados e o programador escreve diretamente num parâmetro do componente filho.
- O componente filho é renderizado com um ou mais valores de parâmetro do componente pai.
- O filho escreve diretamente para o valor de um parâmetro.
- O componente pai rerenderiza e sobrescreve o valor do parâmetro do filho.
O potencial de sobrescrever valores de parâmetros estende-se também aos acessores de set do componente filho.
Importante
A nossa orientação geral é não criar componentes que escrevam diretamente nos seus próprios parâmetros depois de o componente ser renderizado pela primeira vez.
Considere o componente ShowMoreExpander seguinte que:
- Renderiza o título.
- Mostra o conteúdo da criança quando selecionado.
- Permite definir o estado inicial com um parâmetro de componente (
InitiallyExpanded).
Após o componente ShowMoreExpander seguinte demonstrar um parâmetro sobrescrito, é mostrado um componente modificado ShowMoreExpander para demonstrar a abordagem correta para este cenário. Os exemplos seguintes podem ser colocados numa aplicação local de exemplos para experienciar os comportamentos descritos.
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;
}
}
O componente ShowMoreExpander é adicionado ao componente pai Expanders seguinte que pode chamar StateHasChanged:
- Chamar StateHasChanged no código do programador notifica um componente de que o seu estado mudou e normalmente agenda a rerenderização do componente para atualizar a interface. StateHasChanged é abordado mais detalhadamente mais tarde no ciclo de vida dos componentes ASP.NET Core Razor e na renderização dos componentes ASP.NET Core Razor.
- O atributo diretivo do
@onclickbotão associa um tratador de eventos ao evento doonclickbotão. A gestão de eventos é abordada em maior detalhe mais tarde no tratamento de eventos noASP.NET Core .
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>
Inicialmente, os ShowMoreExpander componentes comportam-se de forma independente quando as suas InitiallyExpanded propriedades são definidas. Os componentes filhos mantêm os seus estados conforme esperado.
Se StateHasChanged for chamado num componente pai, o Blazor framework rerenderiza os componentes filhos se os seus parâmetros puderem ter mudado:
- Para um grupo de tipos de parâmetros que Blazor verifica explicitamente, Blazor rerenderiza um componente filho se detetar que algum dos parâmetros mudou.
- Para tipos de parâmetros não verificados, Blazor rerenderiza o componente filho independentemente de os parâmetros terem ou não mudado. O conteúdo filho enquadra-se nesta categoria de tipos de parâmetros porque o conteúdo filho é do tipo RenderFragment, que é um delegado que se refere a outros objetos mutáveis.
Para o Expanders componente:
- O primeiro
ShowMoreExpandercomponente define o conteúdo filho em um potencialmente mutável RenderFragment, pelo que uma chamada a StateHasChanged no componente pai renderiza automaticamente o componente e potencialmente sobrescreve o valor deInitiallyExpandedpara o seu valor inicial defalse. - O segundo componente
ShowMoreExpandernão define conteúdo para o filho. Portanto, um potencialmente mutável RenderFragment não existe. Uma chamada para StateHasChanged no componente pai não faz o componente filho ser automaticamente renderizado novamente, por isso o valor deInitiallyExpandeddo componente não é sobrescrito.
Para manter o estado no cenário anterior, use um campo privado no ShowMoreExpander componente para manter o seu estado.
O componente revisto seguinte ShowMoreExpander :
- Aceita o valor do parâmetro do componente pai
InitiallyExpanded. - Atribui o valor do parâmetro componente a um campo privado (
expanded) noOnInitializedevento. - Utiliza o campo privado para manter o seu estado interno de alternância, o que demonstra como evitar escrever diretamente num parâmetro.
Observação
Os conselhos desta secção estendem-se a uma lógica semelhante nos acessores de parâmetros de componentes set, que podem resultar em efeitos secundários indesejáveis semelhantes.
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;
}
}
Observação
O revisado ShowMoreExpander não reflete alterações ao parâmetro InitiallyExpanded após a inicialização (OnInitialized). Em certos cenários, um componente já inicializado pode receber novos valores de parâmetro. Isto pode acontecer, por exemplo, numa vista primária-subordinada onde o mesmo componente é usado para renderizar diferentes vistas de detalhe ou quando o /item/{id} parâmetro de rota muda para mostrar um item diferente.
Considere o componente ToggleExpander seguinte que:
- Permite mudar o estado tanto por dentro como por fora.
- Lida com novos valores de parâmetros, mesmo que a mesma instância de componente seja reutilizada.
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);
}
}
O ToggleExpander componente deve ser usado com a @bind-Expanded="{field}" sintaxe de ligação, permitindo a sincronização bidirecional do parâmetro.
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;
}
}
Para mais informações sobre a vinculação entre pais e filhos, consulte os seguintes recursos:
- Ligação com parâmetros do componente
- Vincular através de mais de dois componentes
- Blazor Erro de Ligação Bidirecional (dotnet/aspnetcore #24599)
Para mais informações sobre deteção de alterações, incluindo informações sobre os tipos exatos que Blazor verifica, veja ASP.NET Core Razor Renderização de Componentes.