Vykreslování komponent ASP.NET Core Razor
Poznámka:
Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Důležité
Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.
Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Tento článek vysvětluje Razor vykreslování komponent v aplikacích ASP.NET Core Blazor , včetně toho, kdy volat StateHasChanged ruční aktivaci součásti pro vykreslení.
Konvence vykreslování pro ComponentBase
Komponenty se musí vykreslit při prvním přidání do hierarchie komponent nadřazenou komponentou. Jedná se o jediný čas, kdy se komponenta musí vykreslit. Komponenty se můžou vykreslit v jiných časech podle vlastní logiky a konvencí.
Ve výchozím nastavení Razor komponenty dědí ze ComponentBase základní třídy, která obsahuje logiku pro aktivaci opětovnéhoenderování v následujících časech:
- Po použití aktualizované sady parametrů z nadřazené komponenty.
- Po použití aktualizované hodnoty pro kaskádový parametr.
- Po oznámení události a vyvolání jednoho z vlastních obslužných rutin událostí
- Po volání vlastní StateHasChanged metody (viz životní cyklus komponent ASP.NET CoreRazor). Pokyny k tomu, jak zabránit přepsání parametrů podřízené komponenty, pokud StateHasChanged je volána v nadřazené komponentě, naleznete v tématu Vyhněte se přepsání parametrů v ASP.NET Core Blazor.
Komponenty zděděné z ComponentBase rerenderů přeskočení kvůli aktualizacím parametrů, pokud platí některé z následujících skutečností:
Všechny parametry pocházejí ze sady známých typů† nebo jakéhokoli primitivního typu , který se od předchozí sady parametrů nezměnil.
† Framework Blazor používá sadu předdefinovaných pravidel a explicitní kontroly typů parametrů pro detekci změn. Tato pravidla a typy se můžou kdykoli změnit. Další informace najdete
ChangeDetection
v rozhraní API v referenčním zdroji ASP.NET Core.Poznámka:
Odkazy na dokumentaci k referenčnímu zdroji .NET obvykle načítají výchozí větev úložiště, která představuje aktuální vývoj pro příští verzi .NET. Pokud chcete vybrat značku pro konkrétní verzi, použijte rozevírací seznam pro přepnutí větví nebo značek. Další informace najdete v tématu Jak vybrat značku verze zdrojového kódu ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Přepsání metody komponenty vrátí (výchozí
ComponentBase
implementace vždy vrátítrue
).false
ShouldRender
Řízení toku vykreslování
Ve většiněpřípadůch ComponentBase Vývojáři obvykle nemusí poskytovat ruční logiku, která říká rozhraní, které komponenty se mají znovu vyřadit a kdy je znovu vyřadit. Celkový účinek konvencí architektury spočívá v tom, že komponenta, která přijímá sám sebe, rekurzivně aktivuje rekurzivní rekendering následnických komponent, jejichž hodnoty parametrů mohly být změněny.
Další informaceoch ASP.NET Blazorch
Vykreslování streamování
Vykreslování streamování se statickým vykreslováním na straně serveru (static SSR) nebo předřažením streamujte aktualizace obsahu v streamu odpovědí a vylepšete uživatelské prostředí pro komponenty, které provádějí dlouhotrvající asynchronní úlohy k úplnému vykreslení.
Představte si například komponentu, která provádí dlouhotrvající databázový dotaz nebo volání webového rozhraní API k vykreslení dat při načítání stránky. Asynchronní úlohy prováděné v rámci vykreslování součásti na straně serveru se obvykle musí dokončit před odesláním vykreslené odpovědi, která může zpozdit načtení stránky. Jakékoli významné zpoždění při vykreslování stránky poškodí uživatelské prostředí. Pokud chcete zlepšit uživatelské prostředí, vykreslování streamování zpočátku vykresluje celou stránku se zástupným obsahem při provádění asynchronních operací. Po dokončení operací se aktualizovaný obsah odešle klientovi ve stejném připojení odpovědi a opraví se do modelu DOM.
Vykreslování streamování vyžaduje, aby se server vyhnul ukládání výstupu do vyrovnávací paměti. Data odpovědi musí při generování dat do klienta tokovat. U hostitelů, kteří vynucují ukládání do vyrovnávací paměti, degraduje vykreslování streamování elegantně a zatížení stránky bez vykreslování streamování.
Chcete-li streamovat aktualizace obsahu při použití statického vykreslování na straně serveru (statické SSR) nebo předběžného vykreslování, použijte [StreamRendering(true)]
atribut na komponentu. Vykreslování streamování musí být explicitně povolené, protože streamované aktualizace můžou způsobit posun obsahu na stránce. Komponenty bez atributu automaticky přijímají vykreslování streamování, pokud nadřazená komponenta tuto funkci používá. Předáním false
atributu v podřízené komponentě zakážete funkci v tomto okamžiku a dále dolů podstrom komponenty. Atribut je funkční při použití na součásti dodané knihovnou Razortříd.
Následující příklad je založený na Weather
komponentě aplikace vytvořené ze Blazor šablony projektu webové aplikace. Volání simulující Task.Delay asynchronní načítání dat o počasí. Komponenta zpočátku vykresluje zástupný obsah ("Loading...
") bez čekání na dokončení asynchronního zpoždění. Po dokončení asynchronního zpoždění a vygenerování obsahu dat o počasí se obsah streamuje do odpovědi a opraví se do tabulky předpovědi počasí.
Weather.razor
:
@page "/weather"
@attribute [StreamRendering(true)]
...
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
...
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
...
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
await Task.Delay(500);
...
forecasts = ...
}
}
Potlačení aktualizace uživatelského rozhraní (ShouldRender
)
ShouldRender se volá při každém vykreslení komponenty. Přepsání ShouldRender pro správu aktualizace uživatelského rozhraní Pokud se implementace vrátí true
, uživatelské rozhraní se aktualizuje.
I když ShouldRender se přepíše, komponenta se vždy vykreslí.
ControlRender.razor
:
@page "/control-render"
<PageTitle>Control Render</PageTitle>
<h1>Control Render Example</h1>
<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++;
}
}
@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++;
}
}
Další informace o osvědčených postupech z hlediska výkonu, které se týkajíShouldRender, najdete v tématu ASP.NET osvědčených postupech pro výkon jádraBlazor.
Kdy zavolat StateHasChanged
Volání StateHasChanged umožňuje kdykoli aktivovat vykreslení. Dávejte ale pozor, abyste zbytečně nezavolali StateHasChanged , což je běžná chyba, která ukládá zbytečné náklady na vykreslování.
Kód by neměl volat StateHasChanged , když:
- Rutinní zpracování událostí, ať už synchronně nebo asynchronně, protože ComponentBase aktivuje vykreslování pro většinu rutinních obslužných rutin událostí.
- Implementace typické logiky životního cyklu, například
OnInitialized
neboOnParametersSetAsync
, ať už synchronně nebo asynchronně, protože ComponentBase aktivuje vykreslení pro typické události životního cyklu.
Může ale dávat smysl volat StateHasChanged v případech popsaných v následujících částech tohoto článku:
- Asynchronní obslužná rutina zahrnuje několik asynchronních fází.
- Příjem volání z něčeho externího Blazor pro systém vykreslování a zpracování událostí
- Vykreslení komponenty mimo podstrom, který je reenderován konkrétní událostí
Asynchronní obslužná rutina zahrnuje několik asynchronních fází.
Vzhledem ke způsobu, jakým jsou úkoly definovány v .NET, může příjemce objektu Task sledovat pouze jeho konečné dokončení, nikoli přechodné asynchronní stavy. ComponentBase Proto se může znovu spustit pouze při Task prvním vrácení a dokončení konečného Task dokončení. Architektura nemůže zjistit, jak rerenderovat komponentu v jiných přechodných bodech, například když IAsyncEnumerable<T>vrátí data v řadě mezilehlých Task
bodů. Pokud chcete znovu zavolat na přechodné body, zavolejte StateHasChanged na tyto body.
Vezměte v úvahu následující CounterState1
komponentu, která aktualizuje počet čtyřikrát při IncrementCount
každém spuštění metody:
- Automatické vykreslení probíhá po prvním a posledním přírůstku .
currentCount
- Ruční vykreslení se aktivuje voláním StateHasChanged , když architektura automaticky neaktivuje rerendery v přechodných bodech zpracování, kde
currentCount
se zvýší.
CounterState1.razor
:
@page "/counter-state-1"
<PageTitle>Counter State 1</PageTitle>
<h1>Counter State Example 1</h1>
<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
}
}
@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
}
}
Příjem volání z něčeho externího Blazor pro systém vykreslování a zpracování událostí
ComponentBase zná pouze své vlastní metody životního cyklu a Blazorudálosti aktivované událostmi. ComponentBase neví o jiných událostech, ke kterým může dojít v kódu. Například všechny události jazyka C# vyvolané vlastním úložištěm dat nejsou známy Blazor. Aby tyto události aktivovaly opětovné zobrazení aktualizovaných hodnot v uživatelském rozhraní, zavolejte StateHasChanged.
Zvažte následující CounterState2
komponentu, která používá System.Timers.Timer k aktualizaci počtu v pravidelných intervalech a voláních StateHasChanged pro aktualizaci uživatelského rozhraní:
OnTimerCallback
spouští se mimo jakýkoli Blazortok vykreslování spravovaného nebo oznámení události. Proto je nutné volatStateHasChanged,OnTimerCallback
protože Blazor neví o změnáchcurrentCount
v zpětném volání.- Komponenta implementuje IDisposable, kde Timer je uvolněna při volání
Dispose
architektury metody. Další informace najdete v tématu Životní cyklus komponent ASP.NET Core Razor.
Vzhledem k tomu, že zpětné volání je vyvoláno mimo Blazorkontext synchronizace, musí komponenta zabalit logiku ComponentBase.InvokeAsyncOnTimerCallback
, aby se přesunula do kontextu synchronizace rendereru. To odpovídá zařazování do vlákna uživatelského rozhraní v jiných architekturách uživatelského rozhraní. StateHasChanged lze volat pouze z kontextu synchronizace rendereru a vyvolá výjimku jinak:
System.InvalidOperationException: Aktuální vlákno není přidruženo k Dispatcheru. Pomocí InvokeAsync() můžete při aktivaci vykreslování nebo stavu součásti přepnout spuštění na Dispečera.
CounterState2.razor
:
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<PageTitle>Counter State 2</PageTitle>
<h1>Counter State Example 2</h1>
<p>
This counter demonstrates <code>Timer</code> disposal.
</p>
<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(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();
}
Vykreslení komponenty mimo podstrom, který je reenderován konkrétní událostí
Uživatelské rozhraní může zahrnovat:
- Odeslání události do jedné komponenty
- Změna stavu.
- Rerendering a zcela jiná komponenta, která není následníkem komponenty přijímající událost.
Jedním ze způsobů, jak tento scénář vyřešit, je poskytnout třídu správy stavu, často jako službu injektáže závislostí (DI), která se vloží do více komponent. Když jedna komponenta volá metodu ve správci stavů, správce stavů vyvolá událost jazyka C#, která je následně přijata nezávislou komponentou.
Přístupy ke správě stavu najdete v následujících zdrojích informací:
- Vytvořte vazbu mezi více než dvěma komponentami pomocí datových vazeb.
- Předávání dat v hierarchii komponent pomocí kaskádových hodnot a parametrů
- Část Služby kontejneru stavu v paměti na straně serveru (ekvivalent na straně klienta) článku Správa stavu
U přístupu správce stavů se události jazyka Blazor C# nacházejí mimo kanál vykreslování. Zavolat StateHasChanged na další komponenty, které chcete znovu v reakci na události správce stavů.
Přístup správce stavů je podobný předchozímu případu System.Timers.Timer v předchozí části. Vzhledem k tomu, že zásobník volání provádění obvykle zůstává v kontextu synchronizace rendereru, InvokeAsync volání se obvykle nevyžaduje. Volání InvokeAsync je vyžadováno pouze v případě, že logika unikne kontextu synchronizace, například volání ContinueWith na Task volání nebo čekání na s TaskConfigureAwait(false)
. Další informace najdete v části Příjem volání z něčeho externího Blazor z části systému vykreslování a zpracování událostí.
Indikátor průběhu načítání WebAssembly pro Blazor Web Apps
Indikátor průběhu načítání není v aplikaci vytvořené ze Blazor šablony projektu webové aplikace. Pro budoucí verzi .NET se plánuje nová funkce indikátoru průběhu načítání. Mezitím může aplikace přijmout vlastní kód a vytvořit indikátor průběhu načítání. Další informace najdete v tématu ASP.NET Spuštění coreBlazor.
Váš názor
https://aka.ms/ContentUserFeedback.
Připravujeme: V průběhu roku 2024 budeme postupně vyřazovat problémy z GitHub coby mechanismus zpětné vazby pro obsah a nahrazovat ho novým systémem zpětné vazby. Další informace naleznete v tématu:Odeslat a zobrazit názory pro