Blazor コンポーネントのライフサイクル: 完全なガイド

· 3分で読める

私はしばらく Blazor を使用してきましたが、正直に言って、最初はライフサイクルの方法に戸惑いました。 OnInitializedOnInitializedAsync? OnParametersSet vs OnAfterRender? StateHasChanged が実際に再レンダリングをトリガーするのはいつですか?多くの試行錯誤を経て、最終的にすべてについての確固たるメンタルモデルができました。

ライフサイクルの概要

Blazor コンポーネントがレンダリングされるとき、次のメソッドが順番に実行されます。

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

それぞれについて見ていきましょう。

SetParametersAsync

これは最初に呼び出されるメソッドです。親コンポーネントから生の ParameterView を受け取ります。ほとんどの場合、これをオーバーライドすることはありません。Blazor はマッピング パラメーターを自動的に処理します。ただし、カスタム パラメーターの処理または割り当て前に検証が必要な場合は、次のようにします。

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

実際のプロジェクトでこれを一度だけ使用しました。ほとんどの場合はスキップします。

OnInitialized / OnInitializedAsync

ここで、データのロード、サービスの初期化、デフォルト値の設定などのセットアップ作業を行います。これは、コンポーネントが最初に作成されたときに 1 回実行されます。

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

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

私をつまずかせた点が 1 つあります。Blazor サーバーでは、プリレンダリング中に OnInitializedAsync2 回呼び出されます。 1 回目はサーバー側のプリレンダリング中、2 回目は SignalR 接続の確立時に行われます。 API 呼び出しが高価な場合は、次のように処理することをお勧めします。

private bool isPrerendering = true;

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

あるいは、PersistentComponentState を使用して二重呼び出しを完全に回避することもできます。

OnParametersSet / OnParametersSetAsync

これは、親コンポーネントが再レンダリングされ、新しいパラメータ値が渡されるたびに起動されます。 OnInitialized の後にも起動されます。ここはパラメータの変更に反応するのに適切な場所です。

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

CategoryId != previousCategoryId のチェックは重要です。これがないと、カテゴリが変更されなかったとしても、親が再レンダリングされるたびにデータをリロードすることになります。

OnAfterRender / OnAfterRenderAsync

これは、コンポーネントが DOM にレンダリングされた後に起動されます。 firstRender パラメータは、それが最初のレンダリングであるかどうかを示します。この時点で DOM 要素が存在するため、これが JS Interop 呼び出しの場所です。

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

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

再レンダリングのたびに JavaScript ライブラリが再初期化されるのを避けるために、私は firstRender をよく使用します。イベント リスナー、チャート ライブラリ、または DOM に直接関わるものを設定している場合は、ここで設定します。

ShouldRender

これはあまり知られていませんが、非常に便利です。 StateHasChanged が呼び出されたときにコンポーネントを再レンダリングするかどうかを制御します。

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
}

私はこれを使用して、大きなリストを処理するコンポーネントを最適化しました。項目が変更されるたびに再レンダリングするのではなく、更新をバッチ処理し、最後に 1 回レンダリングします。

破棄 / DisposeAsync

コンポーネントが UI から削除された場合は、リソースをクリーンアップする必要があります。 IDisposable または 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();
        }
    }
}

破棄する一般的なもの: タイマー、イベント サブスクリプション、JS モジュール参照、CancellationTokenSource、および作成した IDisposable サービス。

状態が変化しましたこれはライフサイクル手法ではありませんが、密接に関連しています。これは Blazor に「状態が変わったので、再レンダリングしてください」と伝えます。 Blazor はイベント ハンドラーの後に自動的にこれを呼び出しますが、通常は通常の Blazor イベント フローの外側から状態が変化する場合に、手動で呼び出す必要がある場合があります。

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

重要な注意事項: Blazor Server のバックグラウンド スレッドから更新している場合は、 InvokeAsync を使用してください。

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

全体像

すべてが起こる順序は次のとおりです。

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

このフローを頭の中に入れておくと、ライフサイクルの問題のデバッグがはるかに簡単になります。

投稿が気に入っていただければ幸いです!ソーシャルメディアで**@emimontesdeoca**までお気軽にご連絡ください。

リソース