Lebenszyklus von Blazor-Komponenten: der vollständige Leitfaden

· 5 Min. Lesezeit

Ich benutze Blazor jetzt schon eine Weile und ehrlich gesagt haben mich die Lebenszyklusmethoden zunächst verwirrt. OnInitialized vs. OnInitializedAsync? OnParametersSet vs. OnAfterRender? Wann löst StateHasChanged tatsächlich ein erneutes Rendern aus? Nach vielen Versuchen und Irrtümern habe ich endlich ein solides mentales Modell für alles.

Der Lebenszyklus auf einen Blick

Wenn eine Blazor-Komponente gerendert wird, durchläuft sie diese Methoden der Reihe nach:

  1. SetParametersAsync
  2. OnInitialized / OnInitializedAsync
  3. OnParametersSet / OnParametersSetAsync
  4. OnAfterRender / OnAfterRenderAsync
  5. Dispose / DisposeAsync

Lassen Sie uns jeden einzelnen durchgehen.

SetParametersAsync

Dies ist die allererste aufgerufene Methode. Es empfängt den Rohdatensatz ParameterView von der übergeordneten Komponente. Meistens überschreiben Sie dies nicht – Blazor verarbeitet die Zuordnungsparameter automatisch. Wenn Sie jedoch eine benutzerdefinierte Parameterbehandlung oder -validierung benötigen, bevor sie zugewiesen werden:

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

Ich habe das genau einmal in einem echten Projekt verwendet. Meistens überspringst du es.

OnInitialized / OnInitializedAsync

Hier erledigen Sie Ihre Einrichtungsarbeiten: Laden Sie Daten, initialisieren Sie Dienste und legen Sie Standardwerte fest. Es wird einmal ausgeführt, wenn die Komponente zum ersten Mal erstellt wird.

@code {
    private List<Product> products = new();

    protected override async Task OnInitializedAsync()
    {
        products = await Http.GetFromJsonAsync<List<Product>>("api/products");
    }
}

Eine Sache hat mich gestolpert: In Blazor Server wird OnInitializedAsync beim Vorrendern zweimal aufgerufen. Das erste Mal während des serverseitigen Prerenders und das zweite Mal, wenn die SignalR-Verbindung hergestellt wird. Wenn Ihr API-Aufruf teuer ist, möchten Sie vielleicht Folgendes erledigen:

private bool isPrerendering = true;

protected override async Task OnInitializedAsync()
{
    products = await Http.GetFromJsonAsync<List<Product>>("api/products");
    isPrerendering = false;
}

Oder noch besser: Verwenden Sie PersistentComponentState, um den Doppelaufruf vollständig zu vermeiden.

OnParametersSet / OnParametersSetAsync

Dies wird jedes Mal ausgelöst, wenn die übergeordnete Komponente erneut gerendert wird und neue Parameterwerte übergibt. Es wird auch nach OnInitialized ausgelöst. Hier sind Sie richtig, um auf Parameteränderungen zu reagieren:

[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}");
    }
}

Die Prüfung auf CategoryId != previousCategoryId ist wichtig – ohne sie würden Sie die Daten jedes Mal neu laden, wenn das übergeordnete Element erneut gerendert wird, selbst wenn sich die Kategorie nicht geändert hätte.

OnAfterRender / OnAfterRenderAsync

Dies wird ausgelöst, nachdem die Komponente im DOM gerendert wurde. Der Parameter firstRender sagt Ihnen, ob es sich um das erste Rendering handelt. Dies ist der Ort für JS-Interop-Aufrufe, da die DOM-Elemente an dieser Stelle vorhanden sind:

[Inject]
private IJSRuntime JS { get; set; }

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await JS.InvokeVoidAsync("initializeChart", chartElement);
    }
}

Ich verwende firstRender häufig, um zu vermeiden, dass JavaScript-Bibliotheken bei jedem erneuten Rendern neu initialisiert werden. Wenn Sie Ereignis-Listener, Diagrammbibliotheken oder alles, was das DOM direkt berührt, einrichten, ist dies der richtige Ort.

ShouldRender

Dieser ist weniger bekannt, aber super nützlich. Es steuert, ob eine Komponente erneut gerendert wird, wenn StateHasChanged aufgerufen wird:

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
}

Ich habe dies verwendet, um Komponenten zu optimieren, die große Listen verarbeiten. Anstatt bei jeder Elementänderung erneut zu rendern, stapeln Sie die Aktualisierungen und rendern sie am Ende einmal.

Entsorgen / DisposeAsync

Wenn eine Komponente von der Benutzeroberfläche entfernt wird, sollten Sie alle Ressourcen bereinigen. Implementieren Sie IDisposable oder 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();
        }
    }
}

Häufig zu entsorgende Dinge: Timer, Ereignisabonnements, JS-Modulreferenzen, CancellationTokenSource und alle von Ihnen erstellten IDisposable-Dienste.

StateHasChangedDies ist keine Lebenszyklusmethode, aber sie ist eng damit verbunden. Es sagt Blazor: „Hey, mein Zustand hat sich geändert, bitte rendern Sie mich neu.“ Blazor ruft es automatisch nach Ereignishandlern auf, aber manchmal müssen Sie es manuell aufrufen – normalerweise, wenn sich der Status außerhalb des normalen Blazor-Ereignisflusses ändert:

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

Ein wichtiger Hinweis: Wenn Sie von einem Hintergrundthread in Blazor Server aktualisieren, verwenden Sie InvokeAsync:

await InvokeAsync(() =>
{
    data = newData;
    StateHasChanged();
});

Das ganze Bild

Hier ist die Reihenfolge, in der alles passiert:

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

Sobald Sie diesen Ablauf im Kopf haben, wird das Debuggen von Lebenszyklusproblemen viel einfacher.

Ich hoffe, Ihnen hat der Beitrag gefallen! Sie können mich gerne in den sozialen Medien unter @emimontesdeoca kontaktieren.

Ressourcen