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.

Upozorňující

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v tématu .NET a .NET Core Zásady podpory. 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í.

Razor komponenty dědí ze ComponentBase základní třídy, která obsahuje logiku pro aktivaci rerenderingu v následujících časech:

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í:

Ří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ě v aplikaci vytvořené ze Blazor Web App šablony projektu. 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() => shouldRender;

    private void IncrementCount() => currentCount++;
}
@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() => 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.

StateHasChanged

Volání StateHasChanged enqueues rerender k výskytu, když hlavní vlákno aplikace je zdarma.

Komponenty jsou vyřazeny do fronty pro vykreslování a nejsou znovu vyřazeny, pokud již existuje čekající rerender. Pokud komponenta volá StateHasChanged pětkrát za sebou ve smyčce, komponenta se vykreslí jen jednou. Toto chování je zakódováno , ComponentBasecož nejprve zkontroluje, jestli je zařazený do fronty před zařazením do fronty dalšího.

Komponenta se může během stejného cyklu vykreslit vícekrát, což se běžně vyskytuje, když má komponenta podřízené položky, které vzájemně spolupracují:

  • Nadřazená komponenta vykreslí několik podřízených položek.
  • Podřízené komponenty vykreslují a aktivují aktualizaci nadřazeného objektu.
  • Nadřazená komponenta znovu vyřazuje nový stav.

Tento návrh umožňuje StateHasChanged volat v případě potřeby bez rizika zavedení zbytečného vykreslování. Toto chování můžete vždy převzít v jednotlivých komponentách implementací IComponent přímo a ručně, když se komponenta vykreslí.

Vezměte v úvahu následující IncrementCount metodu, která zvýší počet, volání StateHasChangeda zvýší počet znovu:

private void IncrementCount()
{
    currentCount++;
    StateHasChanged();
    currentCount++;
}

Krokování kódu v ladicím programu si můžete myslet, že počet aktualizací v uživatelském rozhraní pro první currentCount++ spuštění ihned po StateHasChanged zavolání. Uživatelské rozhraní ale v tomto okamžiku nezobrazuje aktualizovaný počet kvůli synchronnímu zpracování, které probíhá při provádění této metody. Renderer nemá příležitost vykreslit komponentu, dokud se obslužná rutina události nedokončí. Uživatelské rozhraní se zobrazí u obou currentCount++ spuštění v jednom vykreslení.

Pokud očekáváte něco mezi currentCount++ řádky, očekávané volání dává renderer šanci vykreslit. To vedlo k tomu, že někteří vývojáři volali Delay s jedním milisekundovým zpožděním v jejich komponentách, aby bylo možné vykreslit, ale nedoporučujeme libovolně zpomalovat aplikaci, aby se vykreslení vytvořilo.

Nejlepší je očekávat Task.Yield, který vynutí komponentu zpracovat kód asynchronně a vykreslit během aktuální dávky s druhým vykreslením v samostatné dávce po spuštění pokračování.

Vezměte v úvahu následující revidovanou IncrementCount metodu, která aktualizuje uživatelské rozhraní dvakrát, protože vykreslení StateHasChanged ve frontě je provedeno při provedení úlohy s voláním Task.Yield:

private async Task IncrementCount()
{
    currentCount++;
    StateHasChanged();
    await Task.Yield();
    currentCount++;
}

Dávejte 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 nebo OnParametersSetAsync, 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í.

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 Taskbodů. 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"

<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ách currentCount 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.InvokeAsync OnTimerCallback, 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

<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:

  1. Odeslání události do jedné komponenty
  2. Změna stavu.
  3. 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í:

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 Task ConfigureAwait(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 Web App šablony projektu. 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.