5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Blazor ベストプラクティス】ShouldRenderを利用して不必要なレンダリングを避ける

Posted at

概要

ページを構成するコンポーネントは親子関係になっていることが大半で、親がレンダリングされたら、子もレンダリングされ、さらに孫も、さらにひ孫も・・・・というように再レンダリングが末端まで続きます。

しかし!

Blazorにはデフォルトで、再レンダリングを実行するかどうかを判断する機能がついているんです!

本記事ではそれを有効利用する方法について紹介したいと思います。

再レンダリングの流れ

親コンポーネントでイベントが発生したときのレンダリングの流れは以下になります。

  1. イベント実行を完了した親コンポーネントが再レンダリングされる
  2. 再レンダリングの際、子コンポーネントに渡すパラメータには新たなコピーが渡される
  3. パラメータを受け取った子コンポーネントはパラメータ値に変化があるかをチェックし、変化があれば再レンダリングを行い、なければ再レンダリングしない

めちゃくちゃ気が利いていますね!もうこれで紹介が終わりそうな感じもしますが、知っておかなければいけない癖があります。

それは、パラメータ値がプリミティブ型の場合のみ、変化有無の判断が行われるという点です。自作クラスなどをパラメータとして渡す場合は問答無用で再レンダリングされます。

※これは私の理解ですが、上記の手順2において参照型は新たな参照値を生成し、レンダリングの度に異なる値となるため問答無用でレンダリングが走ると認識しています。

ShouldRenderの活用

自作クラスなどを渡す場合でも、ShouldRenderを有効利用することで不要なレンダリングを防ぐことができます。

サンプルで確認

確認に必要なコンポーネント群を準備します。各コンポーネントのOnAfterRenderでレンダリング有無のログを確認します。

Blazorプロジェクトは、WebAssemblyで作成しています。

※もちろんServerでも確認可能ですが、ログ出力の部分をSystem.Diagnostics.Debug.Writeに変更する必要があります。

  • パラメータ無しコンポーネント
NoParamComponent.razor
<h3>NoParamComponent</h3>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        if (!firstRender)
            Console.WriteLine("NoParamComponent");
    }
}
  • プリミティブ型パラメータ有りコンポーネント
PrimitiveParamComponent.razor
<h3>PrimitiveParamComponent</h3>

@code {
    [Parameter]
    public string Title { get; set; } = "";

    protected override void OnAfterRender(bool firstRender)
    {
        if (!firstRender)
            Console.WriteLine("Primitive Param Component");
    }
}
  • 自作クラス型パラメータ有りコンポーネント
Models/UserInfo.cs
namespace YourProject.Models;

public class UserInfo
{
    public string Name { get; set; } = "";
    public string Email { get; set; } = "";
}
CustomClassComponent.razor
@using YourProject.Models
<h3>CustomClassParamComponent</h3>

@code {
    [Parameter]
    public UserInfo UserInfo { get; set; } = default!;

    protected override void OnAfterRender(bool firstRender)
    {
        if (!firstRender)
            Console.WriteLine("Custom Class Param Component");
    }
}
  • 親コンポーネント

再レンダリングを発火するボタンを持ちます。

ParentComponent.razor
@page "/effective-rendering/parent-component"
@using BlazorAppTips.Client.Models
@rendermode InteractiveWebAssembly

<button @onclick="() => isToggle = !isToggle" style="border: 1px solid black">Click</button>
@isToggle
<NoParamComponent />
<PrimitiveParamComponent Title=@title />
<CustomClassParamComponent UserInfo=@userInfo />

@code {
    private bool isToggle = false;
    private string title = "title";
    private UserInfo userInfo = new UserInfo();

    protected override void OnAfterRender(bool firstRender)
    {
        if (!firstRender)
            Console.WriteLine("Parent Component");
    }
}

ShouldRender無しの場合

http://localhost:[Your port]/effective-rendering/parent-componentにアクセスし、Clickを押下します。

BrowserのコンソールログからParentComponentCustomClassComponentがレンダリングされていることが確認できます。

userInfo値は何一つ変わっていないのに、再レンダリングが実行されていることが分かりますね。

ちなみに、NoParamComponentはパラメータ値が存在しないので、再レンダリングが行われず、PrimitiveParamComponentはパラメータ値がプリミティブ型かつ値も変わっていないので再レンダリングが発火されていません。

ShouldRender有りの場合

ShouldRenderを利用するように、CustomClassComponent.razorを以下のように変更してください。

前回値と再設定されるパラメータ値を比較し、異なっていればShouldRendertrueが格納され再レンダリングを実行します。

この状態で、Clickを押下すると、ParentComponentのみレンダリングされていることが分かりますね!

CustomClassComponent.razor
@using YourProject.Models
<h3>CustomClassParamComponent</h3>

@code {
    private string prevUserInfoName = "";
    private string prevUserInfoEmail = "";
    private bool shouldRender = false;

    [Parameter]
    public UserInfo UserInfo { get; set; } = default!;

    protected override void OnAfterRender(bool firstRender)
    {
        if (!firstRender)
            Console.WriteLine("Custom Class Param Component");
    }

    protected override void OnParametersSet()
    {
        // Judge this component shyould render or not
        shouldRender = prevUserInfoName != UserInfo.Name || prevUserInfoEmail != UserInfo.Email;

        prevUserInfoName = UserInfo.Name;
        prevUserInfoEmail = UserInfo.Email;
    }

    protected override bool ShouldRender() => shouldRender;
}

最後に

Callbackを渡したい場合等も同様にShouldRenderを用いて制御可能です。アプリケーション規模が大きくなればなるほど、こういった効率化が役に立ってきます。

ただ機能を実装するのではなく、フレームワークを駆使して効率的な開発をできるように心がけていきたいものです。

リポジトリ

BlazorAppTips

参考

Avoid unnecessary rendering of component subtrees

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?