Udostępnij za pośrednictwem


renderowanie składników ASP.NET Core Razor

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z obecną wersją, zobacz wersję tego artykułu platformy .NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wersję artykułu .NET 9.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu .NET 9.

W tym artykule opisano Razor renderowanie składników w aplikacjach ASP.NET Core Blazor , w tym czas wywołania StateHasChanged w celu ręcznego wyzwolenia składnika do renderowania.

Konwencje renderowania dla ComponentBase

Składniki muszą być renderowane po pierwszym dodaniu ich do hierarchii składników przez składnik nadrzędny. Jest to jedyny moment, kiedy składnik musi zostać wyrenderowany. Składniki mogą być renderowane w innych momentach zgodnie z własną logiką i konwencjami.

Razor składniki dziedziczą z klasy bazowej ComponentBase, która zawiera logikę wyzwalającą rerendering w następujących momentach:

Komponenty dziedziczone po ComponentBase pomijają ponowne renderowanie z powodu aktualizacji parametrów, jeśli spełniony jest którykolwiek z następujących warunków:

  • Wszystkie parametry pochodzą z zestawu znanych typów† lub dowolnego typu pierwotnego, który nie zmienił się od czasu ustawienia poprzedniego zestawu parametrów.

    † Framework Blazor używa zestawu wbudowanych reguł i jawnego sprawdzania typu parametrów na potrzeby wykrywania zmian. Te reguły i typy mogą ulec zmianie w dowolnym momencie. Aby uzyskać więcej informacji, zobacz ChangeDetection interfejs API w źródle referencyjnym ASP.NET Core.

    Uwaga

    Linki dokumentacji do źródła referencyjnego .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące prace rozwojowe nad następnym wydaniem .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).

  • Zastąpienie metody składnika zwraca (domyślna implementacja zawsze zwraca ).

Sterowanie przepływem renderowania

W większości przypadków ComponentBase konwencje powodują prawidłowe ponowne renderowanie podzbioru składników po wystąpieniu zdarzenia. Deweloperzy zwykle nie muszą zapewniać ręcznej logiki, aby poinformować framework, które komponenty należy ponownie renderować i kiedy to robić. Ogólny wpływ konwencji struktury polega na tym, że składnik otrzymujący zdarzenie ponownie renderuje siebie, co rekursywnie inicjuje ponowne renderowanie składników potomnych, których wartości parametrów mogły ulec zmianie.

Aby uzyskać więcej informacji na temat wpływu konwencji platformy na wydajność i sposobu optymalizowania hierarchii składników aplikacji na potrzeby renderowania, zobacz najlepsze praktyki dotyczące wydajności Blazor ASP.NET Core.

Renderowanie przesyłania strumieniowego

Użyj renderowania strumieniowego wraz z renderowaniem statycznym po stronie serwera (statyczne SSR) lub wstępnym renderowaniem, aby przesyłać aktualizacje zawartości w strumieniu odpowiedzi i ulepszyć doświadczenie użytkownika dla komponentów, które wykonują długotrwałe asynchroniczne zadania wymagające pełnego renderowania.

Rozważmy na przykład składnik, który tworzy długotrwałe zapytanie bazy danych lub wywołanie internetowego interfejsu API w celu renderowania danych podczas ładowania strony. Zwykle zadania asynchroniczne wykonywane w ramach renderowania składnika po stronie serwera muszą zostać wykonane przed wysłaniem renderowanej odpowiedzi, co może opóźnić ładowanie strony. Wszelkie znaczne opóźnienia w renderowaniu strony szkodzą środowisku użytkownika. Aby poprawić doświadczenie użytkownika, renderowanie strumieniowe początkowo szybko renderuje całą stronę przy użyciu zawartości zastępczej, podczas gdy wykonywane są operacje asynchroniczne. Po zakończeniu operacji zaktualizowana zawartość jest wysyłana do klienta na tym samym połączeniu odpowiedzi i wstawiana do modelu DOM.

Renderowanie strumieniowe wymaga, aby serwer unikał buforowania danych wyjściowych. Dane odpowiedzi muszą przepływać do klienta w miarę generowania danych. W przypadku hostów, które wymuszają buforowanie, renderowanie przesyłania strumieniowego degraduje się płynnie, a strona ładuje się bez tego renderowania.

Aby przesyłać strumieniowo aktualizacje zawartości podczas korzystania ze statycznego renderowania po stronie serwera (statycznego SSR) lub prerenderingu, zastosuj atrybut [StreamRendering] w .NET 9 lub nowszym (użyj [StreamRendering(true)] w .NET 8) do składnika. Renderowanie przesyłania strumieniowego musi być jawnie włączone, ponieważ przesyłane strumieniowo aktualizacje mogą spowodować zmianę zawartości na stronie. Składniki bez atrybutu automatycznie przybierają renderowanie streamingowe, jeśli składnik nadrzędny używa tej funkcji. Przekaż false do atrybutu w składniku podrzędnym, aby wyłączyć tę funkcję w tym momencie i dalej w dół poddrzewa składnika. Atrybut jest funkcjonalny po zastosowaniu do składników dostarczanych przez bibliotekę Razorklas.

Poniższy przykład jest oparty na składniku Weather w aplikacji utworzonej na podstawie szablonu Blazor Web Appprojektu. Wywołanie Task.Delay symuluje asynchroniczne pobieranie danych o pogodzie. Składnik początkowo renderuje zawartość symboli zastępczych ("Loading...") bez oczekiwania na zakończenie opóźnienia asynchronicznego. Po zakończeniu opóźnienia asynchronicznego i wygenerowaniu zawartości danych pogodowych, zawartość jest przekazywana do odpowiedzi i wstawiana w tabeli prognozy pogody.

Weather.razor:

@page "/weather"
@attribute [StreamRendering]

...

@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 = ...
    }
}

Zatrzymanie odświeżania interfejsu użytkownika (ShouldRender)

ShouldRender jest wywoływany za każdym razem, gdy składnik jest renderowany. Nadpisz ShouldRender w celu zarządzania odświeżaniem interfejsu użytkownika. Jeśli implementacja zwróci wartość true, interfejs użytkownika zostanie odświeżony.

Nawet jeśli ShouldRender jest zastępowany, składnik jest zawsze renderowany.

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++;
    }
}

Aby uzyskać więcej informacji na temat najlepszych praktyk dotyczących wydajności związanych z ShouldRender, zobacz Najlepsze praktyki wydajności w ASP.NET CoreBlazor.

StateHasChanged

Wywołanie StateHasChanged umieszcza w kolejce ponowne renderowanie, które nastąpi, gdy główny wątek aplikacji będzie wolny.

Składniki są umieszczane w kolejce do renderowania i nie są ponownie dodawane, jeśli istnieje już oczekujące ponowne renderowanie. Jeśli składnik wywołuje StateHasChanged pięć razy z rzędu w pętli, składnik renderuje się tylko raz. To zachowanie jest zakodowane w elemencie ComponentBase, który sprawdza, czy w kolejce znajduje się już zadanie do ponownego renderowania, zanim doda kolejne.

Komponent może renderować się wiele razy w tym samym cyklu, co często ma miejsce, gdy komponent ma dzieci, które współdziałają ze sobą.

  • Komponent nadrzędny renderuje kilka dzieci.
  • Komponenty podrzędne renderują i wyzwalają aktualizację elementu nadrzędnego.
  • Komponent nadrzędny przerysowuje się z nowym stanem.

Ten projekt umożliwia w razie potrzeby wywołanie StateHasChanged bez ryzyka powodowania niepotrzebnego renderowania. Zawsze możesz kontrolować to zachowanie w poszczególnych składnikach, implementując IComponent bezpośrednio i zarządzając ręcznie procesem renderowania składnika.

Rozważmy następującą IncrementCount metodę, która zwiększa licznik, wywołuje StateHasChanged, a następnie znowu zwiększa licznik:

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

Podczas przechodzenia przez kod w debugerze możesz pomyśleć, że licznik aktualizuje się w interfejsie użytkownika przy pierwszym currentCount++ wykonaniu natychmiast po wywołaniu StateHasChanged. Jednak interfejs użytkownika nie pokazuje zaktualizowanej liczby w tym momencie z powodu synchronicznego przetwarzania na potrzeby wykonywania tej metody. Nie ma możliwości renderowania składnika przez program renderujący do zakończenia obsługi zdarzeń. W interfejsie użytkownika są wyświetlane wzrosty dla obu currentCount++ wykonań w jednym renderze.

Jeśli oczekujesz czegoś między wierszami currentCount++ , oczekiwane wywołanie daje renderatorowi szansę renderowania. Doprowadziło to do tego, że niektórzy deweloperzy wywoływali Delay z jednym milisekundowym opóźnieniem w swoich komponentach, aby umożliwić renderowanie, ale nie zalecamy arbitralnego zwalniania aplikacji, aby umieścić renderowanie w kolejce.

Najlepszym rozwiązaniem jest oczekiwanie na Task.Yield, co wymusza, aby komponent przetwarzał kod asynchronicznie i renderował podczas bieżącej partii z drugim renderowaniem w oddzielnej partii po uruchomieniu kontynuacji przez zwrócone zadanie.

Rozważmy następującą poprawioną IncrementCount metodę, która dwukrotnie aktualizuje interfejs użytkownika, ponieważ renderowanie określone przez StateHasChanged w kolejce jest wykonywane, gdy zadanie jest realizowane za pomocą wywołania metody Task.Yield:

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

Uważaj, aby nie wywoływać StateHasChanged niepotrzebnie, co jest typowym błędem, który nakłada niepotrzebne koszty renderowania. Kod nie powinien wywoływać StateHasChanged , gdy:

  • Rutynowa obsługa zdarzeń, zarówno synchroniczna, jak i asynchroniczna, gdyż ComponentBase wyzwala renderowanie w przypadku większości rutynowych procedur obsługi zdarzeń.
  • Implementowanie typowej logiki cyklu życia, takiej jak OnInitialized lub OnParametersSetAsync, zarówno synchronicznie, jak i asynchronicznie, ponieważ ComponentBase wyzwala renderowanie dla typowych zdarzeń cyklu życia.

Jednak wywołanie StateHasChanged w przypadkach opisanych w poniższych sekcjach tego artykułu może mieć sens:

Asynchroniczna procedura obsługi obejmuje wiele faz asynchronicznych

Ze względu na sposób, w jaki zadania są zdefiniowane na platformie .NET, odbiornik obiektu Task może obserwować jego końcowe zakończenie, a nie pośrednie stany asynchroniczne. W związku z tym ComponentBase może wyzwalać ponowne renderowanie tylko wtedy, gdy element Task jest zwracany po raz pierwszy i gdy Task zostanie ostatecznie zakończone. Platforma nie jest w stanie wiedzieć, aby ponownie renderować składnik w innych pośrednich punktach, takich jak gdy zwraca dane w serii pośrednich . Jeśli chcesz ponownie wyrenderować w punktach pośrednich, wywołaj StateHasChanged w tych punktach.

Rozważmy następujący CounterState1 składnik, który aktualizuje liczbę cztery razy za każdym razem, gdy metoda jest wykonywana IncrementCount :

  • Automatyczne renderowanie występuje po pierwszych i ostatnich przyrostach elementu currentCount.
  • Renderowanie ręczne jest inicjowane przez wywołania na StateHasChanged, gdy platforma nie uruchamia automatycznego ponownego renderowania w pośrednich punktach przetwarzania, gdzie currentCount jest zwiększana.

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

Odbieranie połączenia od czegoś zewnętrznego do systemu renderowania i obsługi zdarzeń Blazor

ComponentBase zna tylko swoje metody cyklu życia oraz zdarzenia wyzwalane przez Blazor. ComponentBase nie wie o innych zdarzeniach, które mogą wystąpić w kodzie. Na przykład wszystkie zdarzenia w C# zgłaszane przez niestandardowy magazyn danych są nieznane dla Blazor. Aby takie zdarzenia wyzwalały rerendering w celu wyświetlenia zaktualizowanych wartości w interfejsie użytkownika, wywołaj metodę StateHasChanged.

Rozważ następujący składnik CounterState2, który używa System.Timers.Timer do aktualizowania liczby w regularnych odstępach czasu i wywołuje StateHasChanged w celu zaktualizowania interfejsu użytkownika.

  • OnTimerCallback działa poza dowolnym przepływem renderowania zarządzanym przez Blazor lub powiadomieniem o zdarzeniach. W związku z tym OnTimerCallback musi wywołać StateHasChanged, ponieważ Blazor nie jest świadomy zmian currentCount w wywołaniu zwrotnym.
  • Składnik implementuje IDisposable, gdzie Timer jest usuwany, gdy framework wywołuje metodę Dispose. Aby uzyskać więcej informacji, zobacz usuwanie komponentów w ASP.NET Core Razor.

Ponieważ wywołanie zwrotne jest wykonywane poza kontekstem synchronizacji Blazor, składnik musi opakować logikę OnTimerCallback w ComponentBase.InvokeAsync, aby przenieść ją do kontekstu synchronizacji modułu renderującego. Jest to odpowiednik marshalingu do wątku interfejsu użytkownika w innych strukturach interfejsu użytkownika. StateHasChanged Może być wywoływany tylko z kontekstu synchronizacji modułu renderowania i zgłasza wyjątek w przeciwnym razie:

System.InvalidOperationException: "Bieżący wątek nie jest skojarzony z dyspozytorem. Użyj metody InvokeAsync(), aby przełączyć wykonywanie na dyspozytor podczas wyzwalania renderowania lub stanu składnika.

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();
}

Aby wyrenderować składnik poza poddrzewem, które jest ponownie wyrenderowane przez określone zdarzenie

Interfejs użytkownika może obejmować:

  1. Wysyłanie zdarzenia do jednego komponentu.
  2. Zmiana stanu.
  3. Ponowne renderowanie zupełnie innego komponentu, który nie jest potomkiem komponentu odbierającego zdarzenie.

Jednym ze sposobów radzenia sobie z tym scenariuszem jest stworzenie klasy zarządzania stanem, często jako usługi wstrzykiwania zależności (DI), która może być wstrzykiwana do wielu komponentów. Gdy jeden składnik wywołuje metodę w menedżerze stanu, menedżer stanu zgłasza zdarzenie języka C#, które jest następnie odbierane przez niezależny składnik.

Aby zapoznać się z metodami zarządzania stanem, zobacz następujące zasoby:

W przypadku podejścia menedżera stanu zdarzenia języka C# znajdują się poza potokiem renderowania Blazor . Wywołaj StateHasChanged dla innych składników, które chcesz przerysować w odpowiedzi na zdarzenia menedżera stanu.

Podejście zarządcy stanu jest podobne do wcześniejszego przypadku z System.Timers.Timer w poprzedniej sekcji. Ponieważ stos wywołań zwykle pozostaje w kontekście synchronizacji modułu renderowania, wywołanie InvokeAsync zwykle nie jest wymagane. Wywołanie InvokeAsync jest wymagane tylko wtedy, gdy logika wychodzi poza kontekst synchronizacji, na przykład wywołanie ContinueWith na Task lub oczekiwanie na Task z ConfigureAwait(false). Aby uzyskać więcej informacji, zobacz sekcję Odbieranie wywołania z elementu zewnętrznego Blazor do systemu renderowania i obsługi zdarzeń.

Wskaźnik postępu ładowania WebAssembly dla Blazor Web Apps

Wskaźnik postępu ładowania nie jest obecny w aplikacji utworzonej na podstawie szablonu Blazor Web App projektu. Planowana jest nowa funkcja wskaźnika postępu ładowania dla przyszłej wersji platformy .NET. W międzyczasie aplikacja może przyjąć niestandardowy kod, aby utworzyć wskaźnik postępu ładowania. Aby uzyskać więcej informacji, zobacz uruchamianie ASP.NET CoreBlazor.