Evitare il flickering dei componenti nel prerender di Blazor 8

di Marco De Sanctis, in ASP.NET Core,

Negli scorsi script abbiamo esplorato le nuove modalità di rendering e routing di Blazor 8, e in particolare delle Blazor Web App. Come abbiamo visto, nel template di default, la nuova pagina Weather.razor sfrutta lo StreamRendering e quindi è renderizzata come non-interactive.

Immaginiamo però di voler aggiungere il sorting alla tabella, e quindi di dover gestire i click dell'utente sugli header:

<table class="table">@page "/weather"
@attribute [StreamRendering]
@rendermode InteractiveWebAssembly

...
@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <thead>
        <tr>
            <th><button href="" @onclick="@(e => SortBy("Date"))">Date</button></th>
            <th><button href="" @onclick="@(e => SortBy("TemperatureC"))">Temp. (C)</button></th>
            <th><button href="" @onclick="@(e => SortBy("TemperatureF"))">Temp. (F)</button></th>
            <th><button href="" @onclick="@(e => SortBy("Summary"))">Summary</button></th>
        </tr>
    </thead>
...

In questo caso, dovremo selezionare una modalità di rendering interattivo, per esempio InteractiveWebAssembly, come nell'esempio in alto. Se ora provassimo a eseguire la pagina, noteremmo un comportamento alquanto fastidioso:

  • la pagina viene renderizzata inizialmente lato server, e, grazie allo StreamRendering, appare immediatamente sul browser con la scritta "loading...";
  • sempre nel contesto server side, una volta che OnInitializedAsync termina e i forecast sono stati popolati, lo StreamRendering inietta nell'HTML in pagina la tabella con i risultati;
  • a questo punto il controllo passa al client: la pagina viene eseguita di nuovo, questa volta sul browser, forecast è nuovamente null, e pertanto riappare di nuovo la scritta "loading...";
  • dopo alcuni decimi di secondo (possiamo incrementare il delay per avere maggiore visibilità), i forecast sono di nuovo popolati e la tabella appare nuovamente visibile.

In buona sostanza, la pagina viene eseguita due volte, una sul server per il prerendering, una sul client dopo il caricamento, e il flickering è causato dal fatto che lo stato della pagina (ossia la variabile forecasts, nel nostro caso) non viene persistito tra l'esecuzione lato server e quella lato client.

Come ovviare a questo problema? La risposta è quella di utilizzare un servizio, chiamato PersistentComponentState, che ci permette di passare informazioni tra render server side e client side.

Il primo passo è quello di iniettarlo in pagina, così che poi possiamo utilizzarlo in OnInitializedAsync:

@page "/weather"
@inject PersistentComponentState ApplicationState
@attribute [StreamRendering]
@rendermode InteractiveWebAssembly
@implements IDisposable

...

@code {
    private WeatherForecast[]? forecasts;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        persistingSubscription =
            ApplicationState.RegisterOnPersisting(SaveState);

        ...
    }

    private Task SaveState()
    {
        ApplicationState.PersistAsJson("data", forecasts);

        Console.WriteLine("Saving state");

        return Task.CompletedTask;
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Disposing Weather component");
        persistingSubscription.Dispose();
    }
}

Nel codice in alto, ancora incompleto, all'interno di OnInitializedAsync creiamo una subscription tramite RegisterOnPersisting, così che possiamo intercettare il momento in cui salvare lo stato della pagina chiamando il metodo SaveState. Quest'ultimo verrà chiamato automaticamente al termine dell'OnInitializedAsync lato server, quindi in uno stato in cui il componente è completamente inizializzato e forecasts è già stato popolato.

In SaveState, possiamo a questo punto salvare forecasts nel dictionary di ApplicationState, invocando PersistAsJson.

Attenzione al fatto che dobbiamo anche cancellare questa subscription nel momento in cui il rendering è terminato, ecco perchè abbiamo anche implementato IDisposable e il metodo Dispose.

Ma come cambia la nostra logica all'interno di OnInitializedAsync? Cerchiamo di capirlo dando un'occhiata alla sua implementazione completa:

protected override async Task OnInitializedAsync()
{
    persistingSubscription =
        ApplicationState.RegisterOnPersisting(SaveState);

    Console.WriteLine("Checking state");

    if (!ApplicationState.TryTakeFromJson<WeatherForecast[]>(
        "data", out var restored))
    {
        Console.WriteLine("State not found: Initial load");

        // Simulate asynchronous loading to demonstrate streaming rendering
        await Task.Delay(2500);

        forecasts = GenerateRandomForecasts();
    }
    else
    {
        Console.WriteLine("Restored from state");
        forecasts = restored;
    }
}

Come possiamo vedere, inizialmente invochiamo TryTakeFromJson per controllare se esista già uno stato memorizzato nell'application state. Al primo rendering (per esempio il prerendering server side), il risultato sarà false, e pertanto procederemo a eseguire la generazione dei forecasts come faremmo normalmente. A questo punto, come abbiamo già detto, il metodo SaveState verrà invocato automaticamente, e lo stato passato al client tramite un apposito frammento HTML:

<!--Blazor-WebAssembly-Component-State:eyJfX2ludGVybmFsX19BbnRpZm9yZ2VyeVJlcXVlc3R....

Lato client, nell'inizializzazione della pagina, ApplicationState verrà re-idratato a partire dall'HTML in alto, così che, quando la pagina sarà in esecuzione sul browser, TryTakeFromJson questa volta restituirà i forecast generati sul server. A questo punto, tutto ciò che dobbiamo fare è assegnarli alla variabile forecast sul client, e la pagina si inizializzerà immediatamente, senza alcun fastidioso flickering.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi