はじめに
自分はウェブアプリを開発するのに、Onsen UI+Vue.js でアプリ開発してきましたが、.NET Blazor を使い始めました。
.NET MAUI+Blazor を始めてみた - Qiita
.NET Blazor を始めてみた
「空の」プロジェクトで始める
Visual Studio で Blazor アプリ開発するプロジェクトを新規作成するとき、「空の」プロジェクトで始めたいものです。
参考:.NET 7 で使えるようになった "空の" Blazor プロジェクトテンプレート vs. "最小の" テンプレート - Qiita
Pages/_Host.cshtml または wwwroot/index.html
自分のアプリに必要のない CSS や JavaScript ライブラリがリンクされていれば削除します。
App.razor
.NET MAUI+Blazor のプロジェクトの場合は Main.razor になります。
<Router>
コンポーネントが配置されています。ページ遷移の機能を使うために残します。
MainLayout.razor
@inherits LayoutComponentBase
<main>
@Body
</main>
どのページに遷移してもアプリ全体で同じレイアウトにするときは、ここにコンポーネントを配置します。
Pages/Index.razor
@page "/"
<h1>Hello, world!</h1>
このファイルを編集します。
コンポーネントを埋込する
参考:BlazorでSPAするぞ!(2) - Component - ryuichi111stdの技術日記
コンポーネントの定義を新規作成します。拡張子 razor
のファイルです。ファイル名がコンポーネント名になります。
<h1>Hello, world!</h1>
作成したコンポーネントをページに埋込してみます。HTML の独自のタグとして使えます。
@page "/"
<Hello />
Index.razor
も実はコンポーネントの定義で、MainLayout.razor
で定義された @Body
に埋込されています。
コンポーネント間で状態を共有する①
コンポーネントは独自のクラスです。あるコンポーネントで定義された変数やメソッドは、他のコンポーネントで使用できません。
親のコンポーネントで定義された変数を、埋込された子のコンポーネントで参照して使用したいことがあります。
子のコンポーネントの定義で変数に [Parameter]
を指定します。↓
<p>Child</p>
<p>Count: @count<p>
@code {
[Parameter]
public int count { get; set; }
}
子コンポーネントでパラメータ指定した変数 count
が、親コンポーネントに埋込した子コンポーネントのタグの属性 count
として値を指定できます。↓
<p>count: @count</p>
<button @onclick="addCount">+</button>
<Child count=@count />
@code {
int count = 0;
void addCount()
{
count += 1;
}
}
親コンポーネントで値を変えると子コンポーネントの値も変わります。
コンポーネント間で状態を共有する②
上記の例は、子のコンポーネントで値を変えても、親のコンポーネントの値は変わりません。
親コンポーネントで定義された変数を、子コンポーネントで参照して変更して、それを親コンポーネントで使用したいことがあります。
子コンポーネントで EventCallback
オブジェクトを用意ます。親から受取した変数を変更したとき、このオブジェクトを Invoke()
します。↓
<p>Child</p>
<p>Count: @count</p>
<button @onclick="addCount">+</button>
@code {
[Parameter]
public int count { get; set; }
[Parameter]
public EventCallback<int> CountChanged { get; set; }
async void addCount()
{
count += 1;
await CountChanged.InvokeAsync(count);
}
}
親コンポーネントから子コンポーネントに値を受渡するのに @bind-変数名
を使います。↓
<p>count: @count</p>
<button @onclick="addCount">+</button>
<Child @bind-count=@count />
以下略
@bind-
を省略した書き方できるようです。↓
<Child count=@count />
子コンポーネントで値を変えると親コンポーネントの値も変わります。
コンポーネント間で状態を共有する③
子コンポーネントで変更したことを親コンポーネントに伝える、他の方法があります。
子コンポーネントの EventCallback
オブジェクトの名前に「On」をつけておきます(つけなくても構いません)。
<p>Child</p>
<p>Count: @count</p>
<button @onclick="addCount">+</button>
@code {
[Parameter]
public int count { get; set; }
[Parameter]
public EventCallback<int> OnCountChanged { get; set; }
async void addCount()
{
count += 1;
await OnCountChanged.InvokeAsync(count);
}
}
親コンポーネントから子コンポーネントにパラメータで値を渡します。子コンポーネントで用意した EventCallback
オブジェクト OnCountChanged
に対して、親コンポーネントでメソッド CountChanged
を用意して、紐づけるようにします。このメソッドは、子コンポーネントから値を引数で受取できます。↓
<p>count: @count</p>
<button @onclick="addCount">+</button>
<Child count=@count OnCountChanged="CountChanged" />
@code {
int count = 0;
void addCount()
{
count += 1;
}
void CountChanged(int count)
{
this.count = count;
}
}
コンポーネント間で状態を共有する④
コンポーネント間で状態を共有する、別の方法です。
参考:ASP.NET Core Blazor 状態管理 | Microsoft Learn
コンテナのクラスを用意して、Program.cs
に追記します。
public class Container
{
private int _count = 0;
public int Count
{
get {
return _count;
}
set {
_count = value;
}
}
}
中略
builder.Services.AddSingleton<Container>();
await builder.Build().RunAsync();
コンポーネントで @inject
します。
<p>Count: @container.Count</p>
<button @onclick="addCount">+</button>
<Child />
@inject Container container
@code {
void addCount()
{
container.Count += 1;
}
}
<p>Child</p>
<p>Count: @container.Count</p>
<button @onclick="addCount">+</button>
@inject Container container
@code {
void addCount()
{
container.Count += 1;
}
}
container
がアプリ全体で参照できる大域変数のように使えます。
ただし、container.count
に代入しても、画面の表示が変わりません。↑
コンテナで Action
オブジェクトを用意して、参照した変数を変更したとき、このオブジェクトを Invoke()
します。↓
public class Container
{
中略
public event Action? OnChange;
中略
public int Count
{
中略
set
{
_count = value;
OnChange?.Invoke();
}
}
コンテナを @inject
しているコンポーネントは、自分の StateHasChanged
メソッドをコンテナの Action
オブジェクトにセットします。これで、コンテナの値が変更されたとき、コンポーネントの StateHasChanged
メソッドが実行されて、画面が再描画されます。↓
中略
@implements IDisposable
@code {
中略
protected override void OnInitialized()
{
container.OnChange += this.StateHasChanged;
}
public void Dispose()
{
container.OnChange -= this.StateHasChanged;
}
中略
@implements IDisposable
@code {
中略
protected override void OnInitialized()
{
container.OnChange += this.StateHasChanged;
}
public void Dispose()
{
container.OnChange -= this.StateHasChanged;
}
コンテナの OnChanged
に StageHasChanged
をセットするのはコンポーネントの OnInitialized
で、逆に Dispose
のタイミングで OnChanged
から StageHasChanged
を外さないといけません。↑
ページ遷移する
参考:BlazorでSPAするぞ!(6) - Routing - ryuichi111stdの技術日記
App.razor
で <Router>
が初期化されています。MainLayout.razor
の @Body
に埋込されるページを切替します。
ページの定義を新規作成します。@page
ディレクティブで URL を指定します。
@page "/next"
<h1>Next Page</h1>
このページに遷移できるようにします。
@page "/"
<a href="next">Next</a>
<NavLink href="next">Next</NavLink>
<button @onclick="GoToNext">Next</button>
@inject NavigationManager nm
@code {
private void GoToNext()
{
nm.NavigateTo("next");
}
}