🐥note.

小鳥とMicrosoft <3 なエンジニアの技術Blog📚

BlazorのLifecycleとShouldRenderの挙動について

こちらのサイトによるとBlazorには以下のLifecycleがあるそうです。

  • OnInitialized
  • OnInitializedAsync
  • OnParametersSet
  • OnParametersSetAsync
  • OnAfterRender
  • OnAfterRenderAsync
  • ShouldRender

ShouldRenderは画面更新前に呼ばれ、画面更新するかどうかの判断結果をboolで返すそうです。

親ページ内に子コンポーネントが存在する際、親側のShouldRenderFalseを返すと子供はその設定に引きずられるのでしょうか?検証してみます。

検証コード

今回の検証はこちらのコードで検証しています。

github.com

検証コードの構成と実行画面

検証の実行画面です。
めっちゃダサいけど検証用なのでデザインは許してください…。

f:id:piyo_esq:20191001185849p:plain
検証コード 実行画面

  • 画面上部のCheckBoxで親・子のShouldRenderの返り値を制御します。

  • Count++ボタンを押下するとCount++ボタンを押下した回数をカウントします。 カウントの変数は親コンポーネントが持っています。

  • 親ページ内に子コンポーネントを配置しています。
    黒い枠線内が子コンポーネントです。

  • 子は親からCount値(Count++ボタンを押した回数)をParameterとして受け取り、子の領域内に表示します。

  • Print Border to Consoleボタンで、Consoleに"-------"という区切り文字を出力します(デバッグ用)

  • ボタン押下時、自動的に画面更新イベント(StateHasChanged)が実行されます。

  • 各ライフサイクルの関数内でConsole.WriteLineによるログを出力しています。

以下、親のコードの一部です

public class ParentComponent : ComponentBase
{
    protected int currentCount = 0;
    protected bool parentRenderFlag = true;
    protected bool childRenderFlag = true;

    protected async Task IncrementCount()
    {
        currentCount++;
        Console.WriteLine($"------------------------");
        Console.WriteLine($"Parent - Count is {currentCount}");
    }

    protected override void OnInitialized()
    {
        Console.WriteLine("Parent - OnInitialized");
    }

    // 途中省略

    protected override void OnAfterRender(bool firsttime)
    {
        Console.WriteLine($"Parent - OnAfterRender({firsttime})");
    }

    // 途中省略

    protected override bool ShouldRender()
    {
        Console.WriteLine($"Parent - ShouldRender({parentRenderFlag})");
        return parentRenderFlag;
    }

    protected void PrintBorderToConsole()
    {
        Console.WriteLine("---------------------");
    }
}

以下、子のコードの一部です

public class ChildComponent : ComponentBase
{
    [Parameter]
    public int ChildNumber { get; set; }
    [Parameter]
    public bool ChildRenderFlag { get; set; }

    protected override void OnInitialized()
    {
        Console.WriteLine("Child - OnInitialized");
    }

    // 途中省略

    protected override void OnAfterRender(bool firsttime)
    {
        Console.WriteLine($"Child - OnAfterRender({firsttime})");
    }

    // 途中省略

    protected override bool ShouldRender()
    {
        Console.WriteLine($"Child - ShouldRender({ChildRenderFlag})");
        return ChildRenderFlag;
    }

    protected void ChildButtonClick()
    {
        Console.WriteLine("--- Child Button Clicked ---");
    }
}

以下、親の画面の一部です

@page "/counter"
@inherits LifeCycleSample.Components.ParentComponent

<div class="container">
    <div class="row">
        <div class="col">
            <p>Parent count: @currentCount</p>
        </div>
    </div>
    <div class="row">
        <div class="col">
            <input type="checkbox" name="parentCheckbox" @bind="@parentRenderFlag" />
            <label for="parentCheckbox">Parent ShouldRender Flag</label>
        </div>

        <!-- 途中省略 -->

        <div class="col">
            <button class="btn btn-primary" @onclick="IncrementCount">Count++</button>
        </div>
    </div>

    <div style="border:1px solid;margin: 10px;padding: 10px">
        <ChildParts ChildNumber=@currentCount ChildRenderFlag=@childRenderFlag></ChildParts>
    </div>

    <!-- 途中省略 -->

</div>

以下、子の画面の一部です

@inherits LifeCycleSample.Components.ChildComponent

<div class="row">
    <div class="col">
        <p>Child count : @ChildNumber</p>
    </div>
</div>
<div class="row">
    <div class="col">
        <button class="btn btn-secondary" @onclick="@ChildButtonClick">Child Print Border to Console</button>
    </div>
</div>

ページ遷移時のLifeCycle

ConsoleのLogは以下の通りです。

Parent - OnInitialized
Parent - OnInitializedAsync
Parent - OnParametersSet
Parent - OnParametersSetAsync
Child - OnInitialized
Child - OnInitializedAsync
Child - OnParametersSet
Child - OnParametersSetAsync
Parent - OnAfterRender(True)
Parent - OnAfterRenderAsync(True)
Child - OnAfterRender(True)
Child - OnAfterRenderAsync(True)
  • OnInitialized -> OnParametersSet -> OnAfterRenderの順に実行されています。
  • ShouldRenderは実行されません。

  • OnAfterRenderの引数がTrue、つまり初回アクセスであることを意味します。

画面更新イベントを発行

以下の状態でCount++ボタンを押下して画面更新イベントを発行します。

  • 親のShouldRenderTrue
  • 子のShouldRenderTrue

f:id:piyo_esq:20191001185922p:plain
親True, 子True でCount++ボタンを押下した結果

ConsoleのLogは以下の通りです。

Parent - Count is 1
Parent - ShouldRender(True)
Child - OnParametersSet
Child - OnParametersSetAsync
Child - ShouldRender(True)
Parent - OnAfterRender(False)
Parent - OnAfterRenderAsync(False)
Child - OnAfterRender(False)
Child - OnAfterRenderAsync(False)
  • Countが1になりました。

  • 親のShouldRender判定後、子のParametersSet, ShouldRender, OnAfterRenderが実行されます。

  • 親子共にShouldRenderの返り値がTrueなので、OnAfterRenderが実行されています。

  • 2回目以降の画面更新なのでOnAfterRenderFalseになっています。

子のShouldRender無効状態で画面更新イベントを発行

以下の状態でCount++ボタンを押下して画面更新イベントを発行します。

  • 親のShouldRenderTrue
  • 子のShouldRenderFalse

f:id:piyo_esq:20191001190040p:plain
親True, 子False でCount++ボタンを押下した結果

ConsoleのLogは以下の通りです。

Parent - Count is 2
Parent - ShouldRender(True)
Child - OnParametersSet
Child - OnParametersSetAsync
Child - ShouldRender(False)
Parent - OnAfterRender(False)
Parent - OnAfterRenderAsync(False)
  • Countが2になりました。

  • 親のCountは2に表示が更新されましたが、子のCountは1のままです。

  • 子のShouldRenderFalseになっているので、子のOnAfterRenderが実行されていません。やはり子コンポーネントの画面は更新されていないようです。

親のShouldRender無効状態で画面更新イベントを発行

以下の状態でCount++ボタンを押下して画面更新イベントを発行します。

  • 親のShouldRenderFalse
  • 子のShouldRenderTrue

f:id:piyo_esq:20191001190101p:plain
親False, 子True でCount++ボタンを押下した結果

ConsoleのLogは以下の通りです。

Parent - Count is 3
Parent - ShouldRender(False)
  • Countが3になりました。

  • 親も子も画面更新されません。

この状態で子のChild Print Border to Consoleボタンを押下して画面更新イベントを発行しても、画面更新はされませんでした。

おわり

親のShouldRenderFalseなら、子のOnParametersSetが発火せず画面は更新されないようです。

ComponentBaseを継承したクラスのコンストラクタで画面要素を更新するような処理を実行すると例外が発生します。"最初の1回目のアクセスで画面が表示された後"という条件であればOnAfterRenderfirsttimetrueの時、という条件でなんとかなりそうですね。

OnParametersSetはパラメータを受け取った前なのか後なのかがよく分かりません。ここはもう少し調査が必要な感じがするので宿題ですね。。