概要
ページを構成するコンポーネントは親子関係になっていることが大半で、親がレンダリングされたら、子もレンダリングされ、さらに孫も、さらにひ孫も・・・・というように再レンダリングが末端まで続きます。
しかし!
Blazorにはデフォルトで、再レンダリングを実行するかどうかを判断する機能がついているんです!
本記事ではそれを有効利用する方法について紹介したいと思います。
再レンダリングの流れ
親コンポーネントでイベントが発生したときのレンダリングの流れは以下になります。
- イベント実行を完了した親コンポーネントが再レンダリングされる
- 再レンダリングの際、子コンポーネントに渡すパラメータには新たなコピーが渡される
- パラメータを受け取った子コンポーネントはパラメータ値に変化があるかをチェックし、変化があれば再レンダリングを行い、なければ再レンダリングしない
めちゃくちゃ気が利いていますね!もうこれで紹介が終わりそうな感じもしますが、知っておかなければいけない癖があります。
それは、パラメータ値がプリミティブ型の場合のみ、変化有無の判断が行われるという点です。自作クラスなどをパラメータとして渡す場合は問答無用で再レンダリングされます。
※これは私の理解ですが、上記の手順2において参照型は新たな参照値を生成し、レンダリングの度に異なる値となるため問答無用でレンダリングが走ると認識しています。
ShouldRender
の活用
自作クラスなどを渡す場合でも、ShouldRender
を有効利用することで不要なレンダリングを防ぐことができます。
サンプルで確認
確認に必要なコンポーネント群を準備します。各コンポーネントのOnAfterRender
でレンダリング有無のログを確認します。
Blazorプロジェクトは、WebAssembly
で作成しています。
※もちろんServer
でも確認可能ですが、ログ出力の部分をSystem.Diagnostics.Debug.Write
に変更する必要があります。
- パラメータ無しコンポーネント
<h3>NoParamComponent</h3>
@code {
protected override void OnAfterRender(bool firstRender)
{
if (!firstRender)
Console.WriteLine("NoParamComponent");
}
}
- プリミティブ型パラメータ有りコンポーネント
<h3>PrimitiveParamComponent</h3>
@code {
[Parameter]
public string Title { get; set; } = "";
protected override void OnAfterRender(bool firstRender)
{
if (!firstRender)
Console.WriteLine("Primitive Param Component");
}
}
- 自作クラス型パラメータ有りコンポーネント
namespace YourProject.Models;
public class UserInfo
{
public string Name { get; set; } = "";
public string Email { get; set; } = "";
}
@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");
}
}
- 親コンポーネント
再レンダリングを発火するボタンを持ちます。
@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のコンソールログからParentComponent
とCustomClassComponent
がレンダリングされていることが確認できます。
userInfo
の値は何一つ変わっていないのに、再レンダリングが実行されていることが分かりますね。
ちなみに、NoParamComponent
はパラメータ値が存在しないので、再レンダリングが行われず、PrimitiveParamComponent
はパラメータ値がプリミティブ型かつ値も変わっていないので再レンダリングが発火されていません。
ShouldRender
有りの場合
ShouldRender
を利用するように、CustomClassComponent.razor
を以下のように変更してください。
前回値と再設定されるパラメータ値を比較し、異なっていればShouldRender
にtrue
が格納され再レンダリングを実行します。
この状態で、Clickを押下すると、ParentComponent
のみレンダリングされていることが分かりますね!
@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
を用いて制御可能です。アプリケーション規模が大きくなればなるほど、こういった効率化が役に立ってきます。
ただ機能を実装するのではなく、フレームワークを駆使して効率的な開発をできるように心がけていきたいものです。