Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
FrameworksIntermédiaireSérie : Blazor / Razor

Blazor : le cycle de vie

Les méthodes de cycle de vie d'un composant : OnInitialized, OnParametersSet, OnAfterRender (firstRender), ShouldRender et StateHasChanged, Dispose, plus le piège du rendu asynchrone et du prerendering.

Par Baptiste Vidal
4 min de lecture
Mis à jour il y a 2 semaines
blazorrazordotnetcycle-de-vierenducomposants

Blazor : le cycle de vie

De sa création à sa destruction, un composant Blazor traverse une séquence de méthodes que tu peux redéfinir pour brancher ta logique au bon moment : charger des données, faire du JS interop, nettoyer. Comprendre l'ordre, c'est éviter les bugs de rendu et les fuites mémoire.


# La séquence

Quand un composant apparaît puis vit, Blazor appelle ses méthodes dans cet ordre :

ÉtapeMéthodeQuandCombien de fois
1OnInitialized[Async]à la création, après les premiers paramètresune fois
2OnParametersSet[Async]après (re)réception des paramètresà chaque changement
3(rendu)Blazor produit le DOMà chaque rendu
4OnAfterRender[Async]après la mise à jour du DOMà chaque rendu
5Dispose / DisposeAsyncà la destructionune fois

ShouldRender() s'intercale avant chaque rendu (sauf le premier) pour l'autoriser ou non ; StateHasChanged() déclenche un rendu à la demande.


# OnInitialized : le point de départ

Appelée une seule fois, c'est l'endroit pour l'initialisation et le chargement des données.

@if (produits is null)
{
    <p>Chargement...</p>
}
else
{
    <ul>@foreach (var p in produits) { <li>@p.Nom</li> }</ul>
}
 
@code {
    private List<Produit>? produits;
 
    protected override async Task OnInitializedAsync()
    {
        produits = await Service.GetProduitsAsync();
    }
}

Rendu avant la fin de l'await : Blazor rend le composant une première fois dès qu'il tombe sur le premier await, donc pendant que les données chargent. C'est pour ça qu'il faut gérer l'état de chargement (produits is null ici) : sans ça, tu tentes d'itérer sur null. Une fois la tâche finie, Blazor re-rend automatiquement.


# OnParametersSet : à chaque changement de paramètre

Appelée après OnInitialized, puis chaque fois que le parent fournit de nouveaux paramètres. C'est ici qu'on réagit à un [Parameter] qui change.

@code {
    [Parameter] public int ClientId { get; set; }
 
    private Commande[]? commandes;
 
    protected override async Task OnParametersSetAsync()
    {
        // Recharge les commandes quand le ClientId change
        commandes = await Service.GetCommandesAsync(ClientId);
    }
}

OnInitialized sert à ce qui ne dépend pas des paramètres (une seule fois) ; OnParametersSet à ce qui en dépend (à chaque changement).


# OnAfterRender : après le rendu du DOM

Appelée une fois le DOM à jour. Le paramètre firstRender distingue le premier rendu des suivants. C'est le seul endroit sûr pour le JS interop (le DOM existe) : initialiser une lib tierce, un graphique, donner le focus.

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync("initGraphique", "#chart");
        }
    }
}

Ne déclenche jamais un rendu sans condition dans OnAfterRender. Appeler StateHasChanged() sans garde provoque : rendu → OnAfterRenderStateHasChanged → rendu → ... boucle infinie. Protège toujours par firstRender ou une condition.


# ShouldRender & StateHasChanged : maîtriser le rendu

Blazor re-rend automatiquement après chaque événement UI et chaque méthode de cycle de vie. Deux leviers pour reprendre la main :

  • StateHasChanged() : forcer un rendu, indispensable quand l'état change hors du flux Blazor (un timer, la complétion d'une tâche async, un événement d'un service).
  • ShouldRender() : bloquer un rendu coûteux quand rien n'a visuellement changé.
@code {
    private int _secondes;
    private Timer? _timer;
 
    protected override void OnInitialized()
    {
        // Le callback du timer tourne hors du contexte de Blazor :
        // InvokeAsync remet le travail sur le bon thread, StateHasChanged rafraîchit.
        _timer = new Timer(_ => InvokeAsync(() =>
        {
            _secondes++;
            StateHasChanged();
        }), null, 0, 1000);
    }
}

# Dispose : le nettoyage

Un composant qui s'abonne à quelque chose (timer, événement d'un service, IJSObjectReference) doit se désabonner quand il disparaît, sinon fuite mémoire. On implémente IDisposable (ou IAsyncDisposable).

@implements IDisposable
 
@code {
    private Timer? _timer;
 
    // ... initialisation du timer ...
 
    public void Dispose() => _timer?.Dispose();
}

C'est exactement ce qu'exige le pattern state container vu dans l'injection de dépendances : OnInitialized s'abonne à OnChange, Dispose s'y désabonne.


# Le piège du prerendering

Avec le prerendering (activé par défaut pour les modes interactifs en .NET 8, voir l'hébergement), OnInitializedAsync s'exécute deux fois : une fois sur le serveur pour produire le HTML initial, une fois quand le composant devient interactif. Résultat : ta requête de données part deux fois.

Pour ne charger qu'une fois, on persiste l'état du prerender avec PersistentComponentState, ou on désactive le prerendering sur le composant si ce n'est pas gênant.


# À retenir

  • Ordre : OnInitialized (une fois) → OnParametersSet (à chaque changement de paramètre) → rendu → OnAfterRenderDispose.
  • OnInitializedAsync rend avant la fin de l'await : gère toujours l'état de chargement.
  • JS interop uniquement dans OnAfterRender, gardé par firstRender ; jamais de StateHasChanged non gardé (boucle infinie).
  • StateHasChanged force un rendu (timer, async, événement externe) ; ShouldRender en évite un inutile.
  • Dispose pour se désabonner (timers, événements, JS) et éviter les fuites.
  • Piège prerendering : OnInitializedAsync s'exécute deux fois ; PersistentComponentState pour ne charger qu'une fois.