Razor rendering del componente ASP.NET Core
Questo articolo illustra il Razor rendering dei componenti nelle app ASP.NET CoreBlazor, tra cui quando chiamare StateHasChanged per attivare manualmente un componente per il rendering.
I componenti devono eseguire il rendering quando vengono prima aggiunti alla gerarchia dei componenti da un componente padre. Questo è l'unico momento in cui deve essere eseguito il rendering di un componente. I componenti possono eseguire il rendering in altre occasioni in base alla propria logica e convenzioni.
Convenzioni di rendering per ComponentBase
Per impostazione predefinita, Razor i componenti ereditano dalla classe base, che contiene la ComponentBase logica per attivare il rerendering nei tempi seguenti:
- Dopo aver applicato un set aggiornato di parametri da un componente padre.
- Dopo aver applicato un valore aggiornato per un parametro a catena.
- Dopo aver inviato una notifica a un evento e richiamando uno dei propri gestori eventi.
- Dopo una chiamata al proprio StateHasChanged metodo (vedere ciclo di vita del componente ASP.NET CoreRazor). Per indicazioni su come evitare di sovrascrivere i parametri dei componenti figlio quando StateHasChanged viene chiamato in un componente padre, vedere ASP.NET Core Razor componenti.
I componenti ereditati dai ComponentBase rerender ignorati a causa degli aggiornamenti dei parametri se uno dei seguenti è true:
Tutti i parametri provengono da un set di tipi noti† o da qualsiasi tipo primitivo che non è stato modificato dal set precedente di parametri.
†Il Blazor framework usa un set di regole predefinite e controlli di tipo di parametro espliciti per il rilevamento delle modifiche. Queste regole e i tipi sono soggetti a modifiche in qualsiasi momento. Per altre informazioni, vedere l'API nell'origine
ChangeDetection
di riferimento ASP.NET Core.Nota
I collegamenti della documentazione all'origine del riferimento .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo corrente per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare l'elenco a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).
Il metodo del
ShouldRender
componente restituiscefalse
.
Controllare il flusso di rendering
Nella maggior parte dei casi, ComponentBase le convenzioni comportano il sottoinsieme corretto dei rerender dei componenti dopo che si verifica un evento. Gli sviluppatori non sono in genere necessari per fornire logica manuale per indicare al framework quali componenti eseguire il rerendere e quando eseguirne il ripristino. L'effetto complessivo delle convenzioni del framework è che il componente che riceve un rerender dell'evento stesso, che attiva in modo ricorsivo il rerendering dei componenti discendenti i cui valori dei parametri potrebbero essere stati modificati.
Per altre informazioni sulle implicazioni delle prestazioni delle convenzioni del framework e su come ottimizzare la gerarchia dei componenti di un'app per il rendering, vedere ASP.NET Core Blazor procedure consigliate per le prestazioni.
Eliminare l'aggiornamento dell'interfaccia utente (ShouldRender
)
ShouldRender viene chiamato ogni volta che viene eseguito il rendering di un componente. Eseguire l'override ShouldRender per gestire l'aggiornamento dell'interfaccia utente. Se l'implementazione restituisce true
, l'interfaccia utente viene aggiornata.
Anche se ShouldRender viene sottoposto a override, il componente viene sempre eseguito il rendering iniziale.
Pages/ControlRender.razor
:
@page "/control-render"
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private bool shouldRender = true;
protected override bool ShouldRender()
{
return shouldRender;
}
private void IncrementCount()
{
currentCount++;
}
}
@page "/control-render"
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private bool shouldRender = true;
protected override bool ShouldRender()
{
return shouldRender;
}
private void IncrementCount()
{
currentCount++;
}
}
@page "/control-render"
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private bool shouldRender = true;
protected override bool ShouldRender()
{
return shouldRender;
}
private void IncrementCount()
{
currentCount++;
}
}
@page "/control-render"
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private bool shouldRender = true;
protected override bool ShouldRender()
{
return shouldRender;
}
private void IncrementCount()
{
currentCount++;
}
}
Per altre informazioni sulle procedure consigliate sulle prestazioni relative a ShouldRender, vedere ASP.NET Core Blazor procedure consigliate per le prestazioni.
Quando chiamare StateHasChanged
La chiamata StateHasChanged consente di attivare un rendering in qualsiasi momento. Tuttavia, prestare attenzione a non chiamare StateHasChanged inutilmente, che è un errore comune che impone costi di rendering non necessari.
Il codice non deve chiamare StateHasChanged quando:
- Gestire regolarmente gli eventi, sia in modo sincrono che asincrono, poiché ComponentBase attiva un rendering per la maggior parte dei gestori eventi di routine.
- Implementazione della logica tipica del ciclo di vita, ad esempio
OnInitialized
oOnParametersSetAsync
, sia in modo sincrono che asincrono, poiché ComponentBase attiva un rendering per gli eventi tipici del ciclo di vita.
Tuttavia, potrebbe essere opportuno chiamare StateHasChanged nei casi descritti nelle sezioni seguenti di questo articolo:
- Un gestore asincrono prevede più fasi asincrone
- Ricezione di una chiamata da un elemento esterno al Blazor sistema di gestione degli eventi e del rendering
- Per eseguire il rendering del componente all'esterno del sottoalbero rendered da un determinato evento
Un gestore asincrono prevede più fasi asincrone
A causa del modo in cui le attività vengono definite in .NET, un ricevitore di un Task può osservare solo il completamento finale, non gli stati asincroni intermedi. Pertanto, ComponentBase può attivare solo il rerendering quando viene restituito per la prima volta e al termine dell'operazione TaskTask . Il framework non è in grado di ripetere il ripristino di un componente in altri punti intermedi, ad esempio quando un IAsyncEnumerable<T>restituisce dati in una serie di s intermediTask
. Se si vuole ripetere il rerender in punti intermedi, chiamare StateHasChanged in questi punti.
Prendere in considerazione il componente seguente CounterState1
, che aggiorna il conteggio quattro volte su ogni clic:
- I rendering automatici si verificano dopo il primo e l'ultimo incremento di
currentCount
. - I rendering manuali vengono attivati dalle chiamate a StateHasChanged quando il framework non attiva automaticamente i rerender in punti di elaborazione intermedi in cui
currentCount
viene incrementato.
Pages/CounterState1.razor
:
@page "/counter-state-1"
<p>
Current count: @currentCount
</p>
<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
// Renders here automatically
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}
@page "/counter-state-1"
<p>
Current count: @currentCount
</p>
<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
// Renders here automatically
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}
@page "/counter-state-1"
<p>
Current count: @currentCount
</p>
<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
// Renders here automatically
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}
@page "/counter-state-1"
<p>
Current count: @currentCount
</p>
<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
// Renders here automatically
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}
Ricezione di una chiamata da un elemento esterno al Blazor sistema di gestione degli eventi e del rendering
ComponentBase conosce solo i propri metodi del ciclo di vita e Blazorgli eventi attivati. ComponentBase non conosce altri eventi che possono verificarsi nel codice. Ad esempio, tutti gli eventi C# generati da un archivio dati personalizzato sono sconosciuti a Blazor. Per consentire a tali eventi di attivare il rerendering per visualizzare i valori aggiornati nell'interfaccia utente, chiamare StateHasChanged.
Si consideri System.Timers.Timer il componente seguente CounterState2
che usa per aggiornare un conteggio a intervalli regolari e le chiamate StateHasChanged per aggiornare l'interfaccia utente:
OnTimerCallback
viene eseguito all'esterno di qualsiasi Blazorflusso di rendering gestito o notifica degli eventi. Pertanto,OnTimerCallback
deve chiamare StateHasChanged perché Blazor non è a conoscenza delle modifiche apportatecurrentCount
al callback.- Il componente implementa IDisposable, dove viene Timer eliminato quando il framework chiama il
Dispose
metodo . Per altre informazioni, vedere Ciclo di vita dei componenti di ASP.NET Core Razor.
Poiché il callback viene richiamato all'esterno del Blazorcontesto di sincronizzazione del componente, il componente deve eseguire il wrapping della logica di OnTimerCallback
in ComponentBase.InvokeAsync per spostarlo nel contesto di sincronizzazione del renderer. Questo equivale al marshalling al thread dell'interfaccia utente in altri framework dell'interfaccia utente. StateHasChanged può essere chiamato solo dal contesto di sincronizzazione del renderer e genera un'eccezione in caso contrario:
System.InvalidOperationException: 'Il thread corrente non è associato al dispatcher. Usare InvokeAsync() per passare all'esecuzione del dispatcher durante l'attivazione del rendering o dello stato del componente.
Pages/CounterState2.razor
:
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>
Current count: @currentCount
</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>
Current count: @currentCount
</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>
Current count: @currentCount
</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>
Current count: @currentCount
</p>
@code {
private int currentCount = 0;
private Timer timer = new Timer(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
Per eseguire il rendering di un componente all'esterno del sottoalbero rendered da un determinato evento
L'interfaccia utente potrebbe comportare:
- Invio di un evento a un componente.
- Modifica di uno stato.
- Rerendering di un componente completamente diverso che non è un discendente del componente che riceve l'evento.
Un modo per gestire questo scenario consiste nel fornire una classe di gestione dello stato , spesso come servizio di inserimento delle dipendenze (DI), inserito in più componenti. Quando un componente chiama un metodo nella gestione stato, gestione stato genera un evento C# che viene quindi ricevuto da un componente indipendente.
Per gli approcci per gestire lo stato, vedere le risorse seguenti:
- Sezione Del servizio contenitore di stato in memoria () (Blazor ServerBlazor WebAssembly equivalente) dell'articolo Gestione stato.
- Passare i dati in una gerarchia di componenti usando i valori e i parametri a catena.
- Eseguire il binding tra più di due componenti usando i data binding.
Per l'approccio di gestione dello stato, gli eventi C# si trovano all'esterno della pipeline di Blazor rendering. Chiamare StateHasChanged altri componenti che si desidera ripetere in risposta agli eventi di gestione dello stato.
L'approccio di gestione dello stato è simile al caso precedente con System.Timers.Timer nella sezione precedente. Poiché lo stack di chiamate di esecuzione rimane in genere nel contesto di sincronizzazione del renderer, la chiamata InvokeAsync non è normalmente necessaria. La chiamata è necessaria solo se la logica esegue l'escape del contesto di sincronizzazione, ad esempio la chiamata InvokeAsyncContinueWith a un Task oggetto o in attesa di un Task oggetto con ConfigureAwait(false)
. Per altre informazioni, vedere la sezione Ricezione di una chiamata da un elemento esterno al rendering e alla Blazor gestione degli eventi .