Cycle de vie des composants Blazor : le guide complet
J’utilise Blazor depuis un moment maintenant et honnêtement, les méthodes de cycle de vie m’ont dérouté au début. OnInitialized contre OnInitializedAsync ? OnParametersSet contre OnAfterRender ? Quand StateHasChanged déclenche-t-il réellement un nouveau rendu ? Après de nombreux essais et erreurs, j’ai enfin un modèle mental solide pour tout cela.
Le cycle de vie en un coup d’œil
Lorsqu’un composant Blazor est rendu, il utilise ces méthodes dans l’ordre :
SetParametersAsyncOnInitialized/OnInitializedAsyncOnParametersSet/OnParametersSetAsyncOnAfterRender/OnAfterRenderAsyncDispose/DisposeAsync
Passons en revue chacun d’entre eux.
SetParametersAsync
C’est la toute première méthode appelée. Il reçoit le ParameterView brut du composant parent. La plupart du temps, vous ne remplacez pas cela – Blazor gère automatiquement les paramètres de mappage. Mais si vous avez besoin d’une gestion ou d’une validation de paramètres personnalisés avant leur attribution :
public override async Task SetParametersAsync(ParameterView parameters)
{
// Custom logic before parameters are set
if (parameters.TryGetValue<string>("Title", out var title))
{
Console.WriteLine($"Title is being set to: {title}");
}
await base.SetParametersAsync(parameters);
}
Je l’ai utilisé exactement une fois dans un vrai projet. La plupart du temps, vous l’ignorerez.
OnInitialized / OnInitializedAsync
C’est ici que vous effectuez votre travail de configuration : charger les données, initialiser les services, définir les valeurs par défaut. Il s’exécute une fois lors de la première création du composant.
@code {
private List<Product> products = new();
protected override async Task OnInitializedAsync()
{
products = await Http.GetFromJsonAsync<List<Product>>("api/products");
}
}
Une chose qui m’a fait trébucher : dans Blazor Server, OnInitializedAsync est appelé deux fois lors du prérendu. La première fois lors du prérendu côté serveur et la deuxième fois lorsque la connexion SignalR est établie. Si votre appel API coûte cher, vous souhaiterez peut-être gérer cela :
private bool isPrerendering = true;
protected override async Task OnInitializedAsync()
{
products = await Http.GetFromJsonAsync<List<Product>>("api/products");
isPrerendering = false;
}
Ou encore mieux, utilisez PersistentComponentState pour éviter complètement le double appel.
OnParametersSet / OnParametersSetAsync
Cela se déclenche chaque fois que le composant parent effectue un nouveau rendu et transmet de nouvelles valeurs de paramètres. Il se déclenche également après OnInitialized. C’est le bon endroit pour réagir aux changements de paramètres :
[Parameter]
public int CategoryId { get; set; }
private int previousCategoryId;
private List<Product> products = new();
protected override async Task OnParametersSetAsync()
{
if (CategoryId != previousCategoryId)
{
previousCategoryId = CategoryId;
products = await Http.GetFromJsonAsync<List<Product>>(
$"api/products?category={CategoryId}");
}
}
La vérification de CategoryId != previousCategoryId est importante : sans elle, vous rechargeriez les données à chaque fois que le parent effectue un nouveau rendu, même si la catégorie n’a pas changé.
OnAfterRender / OnAfterRenderAsync
Cela se déclenche après le rendu du composant dans le DOM. Le paramètre firstRender vous indique s’il s’agit du rendu initial. C’est l’endroit idéal pour les appels JS Interop puisque les éléments DOM existent à ce stade :
[Inject]
private IJSRuntime JS { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("initializeChart", chartElement);
}
}
J’utilise beaucoup firstRender pour éviter de réinitialiser les bibliothèques JavaScript à chaque nouveau rendu. Si vous configurez des écouteurs d’événements, des bibliothèques de graphiques ou tout ce qui touche directement le DOM, c’est ici que cela se passe.
DevraitRender
Celui-ci est moins connu mais super utile. Il contrôle si un composant est restitué lorsque StateHasChanged est appelé :
private bool shouldRender = true;
protected override bool ShouldRender() => shouldRender;
private void HeavyOperation()
{
shouldRender = false;
// Do a bunch of state changes without triggering renders
for (int i = 0; i < 1000; i++)
{
items[i].Process();
}
shouldRender = true;
StateHasChanged(); // Now render once with all changes
}
Je l’ai utilisé pour optimiser les composants qui traitent de grandes listes. Au lieu de refaire le rendu à chaque modification d’élément, vous regroupez les mises à jour et effectuez le rendu une fois à la fin.
Dispose / DisposeAsync
Lorsqu’un composant est supprimé de l’interface utilisateur, vous devez nettoyer toutes les ressources. Implémentez IDisposable ou IAsyncDisposable :
@implements IAsyncDisposable
@code {
private Timer? timer;
private IJSObjectReference? jsModule;
protected override void OnInitialized()
{
timer = new Timer(OnTick, null, 0, 1000);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
jsModule = await JS.InvokeAsync<IJSObjectReference>("import", "./timer.js");
}
}
public async ValueTask DisposeAsync()
{
timer?.Dispose();
if (jsModule is not null)
{
await jsModule.DisposeAsync();
}
}
}
Éléments courants à éliminer : minuteries, abonnements à des événements, références de modules JS, CancellationTokenSource et tous les services IDisposable que vous avez créés.
ÉtatHasChangedIl ne s’agit pas d’une méthode de cycle de vie, mais elle est étroitement liée. Il dit à Blazor “hé, mon état a changé, s’il te plaît, restitue-moi.” Blazor l’appelle automatiquement après les gestionnaires d’événements, mais vous devez parfois l’appeler manuellement - généralement lorsque l’état change en dehors du flux d’événements Blazor normal :
private async Task StartPolling()
{
while (!cts.Token.IsCancellationRequested)
{
data = await Http.GetFromJsonAsync<Data>("api/data");
StateHasChanged(); // Manual call needed since this isn't a Blazor event
await Task.Delay(5000, cts.Token);
}
}
Une remarque importante : si vous effectuez une mise à jour à partir d’un thread en arrière-plan dans Blazor Server, utilisez InvokeAsync :
await InvokeAsync(() =>
{
data = newData;
StateHasChanged();
});
L’image complète
Voici l’ordre dans lequel tout se passe :
Component created
└─ SetParametersAsync
└─ OnInitialized / OnInitializedAsync
└─ OnParametersSet / OnParametersSetAsync
└─ Render
└─ OnAfterRender(firstRender: true)
Parameter change from parent
└─ SetParametersAsync
└─ OnParametersSet / OnParametersSetAsync
└─ ShouldRender?
└─ Render
└─ OnAfterRender(firstRender: false)
Component removed
└─ Dispose / DisposeAsync
Une fois que vous avez ce flux en tête, le débogage des problèmes de cycle de vie devient beaucoup plus facile.
J’espère que vous avez aimé le message ! N’hésitez pas à me contacter sur tous les réseaux sociaux à @emimontesdeoca.