現在の4つのログは、私たちが作成したダミーデータからのものです。しかし、通常はそのような方法は取らず、ユーザーがボタンをクリックしてログの数を増減できるようにします。
追加ボタンは<Blog>に配置し、「追加」ボタンをクリックすると新しいPostが生成されてユーザーが入力できるようになり、さらに「確認」ボタンをクリックするとログが保存されます。
削除ボタンは<Blog>に配置する方法と、<Post>にチェックボックスを追加してユーザー自身に削除したいPostを選択させる方法、または<Post>に削除ボタンを配置してクリックでそのログを削除する方法があります。
追加ボタンは簡単で、<MyButton>に@onclickイベントを追加するだけです。始める前に、レイアウトを少し修正し、ついでにFontSizeStyleを削除します。
Blog.razor
@page "/Blog" @inherits BlogBase @if (Blog == null) {
<p>Loading...</p>
} else {
<div class="container">
<div class="row">
<div class="col pl-0">
<label>博客名称</label>
<input @bind-value="Blog.Name" class="form-control w-25" />
<MyButton
value="Add"
class="btn btn-info my-2"
type="button"
@onclick="Add"
/>
</div>
</div>
<div class="row">
@if (Blog.Posts != null) { foreach (var post in Blog.Posts) {
<div class="col-md-3 border rounded p-3 mr-2 mb-2 w-25">
<CascadingValue Value="ColorStyle" Name="ColorStyle" IsFixed="true">
<Post Post="post" />
</CascadingValue>
</div>
} }
</div>
</div>
}
C#部分にはAdd()メソッドを追加し、元のLoadData()のログは削除します。
BlogBase.razor.cs
using BlazorServer.Models;
using Microsoft.AspNetCore.Components;
namespace BlazorServer.Pages;
public class BlogBase : ComponentBase
{
public BlogModel? Blog { get; set; }
public string? ColorStyle { get; set; } = "color: goldenrod";
public string? FontSizeStyle { get; set; } = "color: goldenrod";
protected override Task OnInitializedAsync()
{
LoadData();
return base.OnInitializedAsync();
}
private void LoadData()
{
Blog = new BlogModel
{
Id = 1,
Name = "我的博客",
Posts = new List<PostModel>(),
CreateDateTime = new DateTime(2021, 12, 14, 23, 46, 59)
};
}
protected void Add()
{
Blog?.Posts?.Add(new PostModel());
}
}
Addボタンをクリックすると、ログの数が増えるのが確認できます。

次にDelete機能を作成します。Post.razorにDeleteボタンを追加します。

しかし問題があります。Deleteボタンをクリックしたとき、<Blog>はどのPostが削除されるのかを知る必要があります。そのためにはIdで識別できる必要があります。そこでプライベート変数_postIdを追加し、Add()が呼ばれるたびに+1します。通常PostIdはPost自体に紐づくものであり、Blogが生成するものではありませんが、まだデータベースに接続していないため、とりあえずこの方法で進め、後でデータベース接続後に変更します。

正しく動作することを確認するため、元のPost.razorのコメントアウトされたPost.Idを削除し、新しいスタイルのPost.Idを追加します。問題なく動作します。

識別用のIdができましたが、新しい問題が発生します。どのようにして<Blog>がIdを受け取るのでしょうか?現在はIdが<Blog>で生成されているため問題ありませんが、今後データベースからIdが生成されるようになると、<Blog>はIdを知らなくなります。これまでは親コンポーネントから子コンポーネントへデータを渡す方法について説明してきましたが、今度は子コンポーネントから親コンポーネントへデータを渡す必要があります。逆方向のデータ伝達は可能でしょうか?
はい、可能です。EventCallbackを使用します。ただし、Deleteボタンは<MyButton>ではなく<input>に変更する必要があります。なぜなら、EventCallbackは子コンポーネントから親コンポーネントへの方向であり、<MyButton>を使用すると、Idの流れは<Blog>⇒<Post>⇒<MyButton>となり、その後EventCallbackで逆方向<MyButton>⇒<Post>⇒<Blog>と戻す必要があり、非常に面倒だからです。
まずDeleteボタンを<input>に変更し、@onclick="ReturnPostId"を追加します。

次にPostBase.razor.csで、型がEventCallback<int>のプロパティGetPostIdを定義します。必ず[Parameter]属性を付けてください。これは<Blog>から呼び出されるためです。そしてReturnPostId()メソッドを完全に定義します。その中で行うのはGetPostId.InvokeAsync(Post!.PostId);です。外部から渡されたGetPostIdがトリガーされると、Post.PostIdを親コンポーネント(<Blog>)に渡します。

次にBlogBase.razor.csで同じ名前のメソッドGetPostId(int id)を定義します。名前は同じである必要はありませんが、ここでは便宜上同じ名前にしています。このメソッドでは、受け取ったIdと同じ値を持つPostを削除します。

最後にBlog.razorの<Post>のGetPostIdに、先ほど定義したメソッドを指定します。

動作確認をしてみましょう。まず4つのログを追加し、次に2番目のログを削除します。Idが2のログが正常に削除されたことが確認できます。

EventCallbackの他にDelegateも使用できますが、制約が大きいです。こちらも試してみましょう。
まずPostBase.razor.csで、型がAction<int>のプロパティGetPostIdForDelegateを定義します。ReturnPostId()では、GetPostIdForDelegateを使用するよう変更します。

次にBlog.razorの<Post>で、GetPostIdForDelegateを使用するよう変更します。

しかし、実際にクリックしてもログは削除されません。これは、EventCallbackがコンポーネントを監視し、変更があれば再レンダリングするのに対し、Delegateは再レンダリングをトリガーしないためです。Delegateを使用する場合は、親コンポーネント(BlogBase.razor.cs)でStateHasChanged();メソッドを呼び出し、親コンポーネントに状態が変更されたことを通知する必要があります。

また、Delegateは子コンポーネントで定義した場合、親コンポーネントで必ず呼び出さなければならず、呼び出さないとエラーが発生します。EventCallbackにはそのような制約はありません。

サイト管理者注:もちろん、nullableを適切に使用することで、このような例外を回避することもできます。

参照:
- Blazor EventCallback
- EventCallback
- Blazor Tutorial - Ep11 - EventCallback and how it is different from delegate
注:この記事のコードは .NET 6 + Visual Studio 2022 でリファクタリングされています。原文リンクとリファクタリング後のコードを比較しながら学習してください。ご協力ありがとうございます。元の作者をサポートします。