入門者向け

【Blazor】Razorコンポーネントのライフサイクルを解説する

component-lifecycle
OnInitialized とかっていつ呼び出されるの?
どんな種類のメソッドがあるのか知りたい。

こんな疑問を解決します。

「よくわからないけど、なんとなく使っている」という人も多いのではないでしょうか?

理解していないまま使うと、思わぬ動作をしてバグの原因となることもあります。

本記事では、OnInitialized などあらかじめ用意されているメソッドの種類と、呼び出されるタイミングについて解説しました。

最後まで読むことで、何がどのタイミングで呼び出されるのかを理解することができます。

ではさっそく見ていきましょう。

用意されているメソッドの定義

コンポーネントのライフサイクル関連について、Blazor で用意されているメソッドを一覧でまとめました。

メソッド名 呼び出し 補足
SetParametersAsync パラメーターが設定されるタイミング
OnInitialized(Async) コンポーネントが初期化されるタイミング
OnParametersSet(Async) コンポーネントが初期化されるタイミングと、受け取るパラメータが更新されたタイミング
OnAfterRender(Async) コンポーネントがレンダリングされたあと 初回実行のときは、引数の firstRender が true となる
ShouldRender コンポーネントがレンダリングされるたび true を返すとレンダリングを続行する
StateHasChanged レンダリングしたい任意のタイミング コストが高いので不必要に呼び出さないこと

おおまかに、以下の流れで実行されます。

実行の順番
  1. SetParametersAsync
  2. OnInitialized(Async)
  3. OnParametersSet(Async)
  4. ShouldRender
  5. OnAfterRender(Async)

それぞれについて、呼び出されるタイミングの検証をしていきましょう。

呼び出されるタイミングの検証

実際に用意されているメソッドが呼び出されるタイミングを検証してみましょう。

プログラムの実装

テンプレートで用意されている Counter.razor ファイルを少し修正してみました。

Counter.razor
@page "/counter/{Param?}"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<button class="btn btn-secondary" @onclick="StateHasChangedClicked">StateHasChanged</button>
<br />

@foreach (var item in eventList)
{
    <span>@item</span>
    <br />
}
<Child Count="currentCount"></Child>

@code {
    [Parameter]
    public string Param { get; set; }

    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }

    List<string> eventList = new List<string>();

    public override Task SetParametersAsync(ParameterView parameters)
    {
        eventList.Add("SetParametersAsync");
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
        }
        return base.SetParametersAsync(parameters);
    }

    protected override void OnInitialized()
    {
        eventList.Add("OnInitialized");
    }

    protected override Task OnInitializedAsync()
    {
        eventList.Add("OnInitializedAsync");
        return base.OnInitializedAsync();
    }

    protected override void OnParametersSet()
    {
        eventList.Add("OnParametersSet");
        base.OnParametersSet();
    }

    protected override Task OnParametersSetAsync()
    {
        eventList.Add("OnParametersSetAsync");
        return base.OnParametersSetAsync();
    }

    protected override void OnAfterRender(bool firstRender)
    {
        eventList.Add("OnAfterRender");
    }

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        eventList.Add("OnAfterRenderAsync");
        return base.OnAfterRenderAsync(firstRender);
    }

    protected override bool ShouldRender()
    {
        eventList.Add("ShouldRender");
        return base.ShouldRender();
    }

    private void StateHasChangedClicked()
    {
        StateHasChanged();
    }

}

ざっくり全体の流れは以下になります。

  1. それぞれのメソッドをオーバーライドする
  2. 呼び出されたメソッド名をリストに追加
  3. リストをループで回して画面に表示

StateHasChanged は任意のタイミングで呼び出すものなので、独自にメソッドを用意しました。

また、子コンポーネントを作ってクリック回数を渡すようにしました。

こちらも同じように実行されたメソッドが画面表示される仕組みです。

Child.razor
<h3>Child Component</h3>

<p> - Child - Current count: @Count</p>

@foreach (var item in eventList)
{
    <span>@item</span>
    <br />
}

@code {
    [Parameter] public int Count { get; set; }

    List<string> eventList = new List<string>();

    public override Task SetParametersAsync(ParameterView parameters)
    {
        eventList.Add(" - Child - SetParametersAsync");
        if (parameters.TryGetValue<int>(nameof(Count), out var value))
        {
        }
        return base.SetParametersAsync(parameters);
    }

    protected override void OnInitialized()
    {
        eventList.Add(" - Child - OnInitialized");
    }

    protected override Task OnInitializedAsync()
    {
        eventList.Add(" - Child - OnInitializedAsync");
        return base.OnInitializedAsync();
    }

    protected override void OnParametersSet()
    {
        eventList.Add(" - Child - OnParametersSet");
        base.OnParametersSet();
    }

    protected override Task OnParametersSetAsync()
    {
        eventList.Add(" - Child - OnParametersSetAsync");
        return base.OnParametersSetAsync();
    }

    protected override void OnAfterRender(bool firstRender)
    {
        eventList.Add(" - Child - OnAfterRender");
    }

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        eventList.Add(" - Child - OnAfterRenderAsync");
        return base.OnAfterRenderAsync(firstRender);
    }

    protected override bool ShouldRender()
    {
        eventList.Add(" - Child - ShouldRender");
        return base.ShouldRender();
    }
}

動作確認(通常)

まずは、画面を表示したあとに「Click me」をクリックする流れをしてみました。

画面の初期表示時点では、SetParametersAsync と OnInitialized と OnParametersSet の3つが親子それぞれで呼び出されていることがわかります。

次に Click me をクリックしてみると、親では Current Count の表記が変わったので、OnAfterRender と ShouldRender が呼び出されました。

子の場合はそれに加え、パラメータで受け取っているカウントが変わったため、OnParametersSet がもう一度呼び出されています。

画面初期表示
component-lifecycle
Click me クリック後
component-lifecycle

動作確認(StateHasChanged)

次に、StateHasChanged の挙動について確認してみます。

画面を初期表示したあと、StateHasChanged のボタンをクリックしてみると、今度は親のイベントだけが動きました。

理由としては以下が考えられます。

  • 子コンポーネントに渡しているパラメータが変化していない
  • 親で StateHasChanged を呼び出しているが、子では呼び出していない
画面初期表示
component-lifecycle
StateHasChanged クリック後
component-lifecycle

StateHasChanged は実行コストが高いので、不要なタイミングでの呼び出しは望ましくありません。

用意されているイベントで変数などを変更した場合は自動的にレンダリングされるので、呼び出しは不要です。

ただし、例えばイベント外から変数を更新した場合などは「変数は更新されるが画面は更新されない」という状況になるため、StateHasChanged を実行する必要があります。

OnAfterRender と ShouldRender の関係

ここまでの検証で、OnAfterRender のあとに ShouldRender が実行されることがわかりました。

一般的に考えると以下の流れになるので、ShouldRender が先にくるように思います。

  1. レンダリングされるかどうかの判定
  2. レンダリングされたあとのイベント

ShouldRender を false で返したときにどうなるかというと、ShouldRender のログも OnAfterRender のログも表示されません。

したがって OnAfterRender と ShouldRender は実行順序ではなく、「実行されたかされないか」が重要になってくるものと考えられます。

最後に

OnInitialized などあらかじめ用意されているメソッドの種類と、呼び出されるタイミングについて解説しました。

最後にもう一度、一覧表を載せておきますね。

メソッド名 呼び出し 補足
SetParametersAsync パラメーターが設定されるタイミング
OnInitialized(Async) コンポーネントが初期化されるタイミング
OnParametersSet(Async) コンポーネントが初期化されるタイミングと、受け取るパラメータが更新されたタイミング
OnAfterRender(Async) コンポーネントがレンダリングされたあと 初回実行のときは、引数の firstRender が true となる
ShouldRender コンポーネントがレンダリングされるたび true を返すとレンダリングを続行する
StateHasChanged レンダリングしたい任意のタイミング コストが高いので不必要に呼び出さないこと

Blazor の書籍も好評発売中!
blazor-book

入門編から EC サイトを作る応用編まで、Blazor の本を3冊執筆しました。

私が1年以上かけて学習した内容をすべて詰め込んでいるので、さらにステップアップしたい方はぜひご覧ください。