Blazor でのコンポーネントの継承
私は多数のフォーム ページを含むプロジェクトを構築していましたが、どのページも同じ読み込み状態ロジック、同じエラー処理、同じトースト通知を持っていました。これらすべてをコピーして貼り付けるのは間違っていると感じたので、Blazor でのコンポーネントの継承について調べました。 Blazor コンポーネントは単なる C# クラスであるため、これは非常に簡単であることがわかります。
基本
すべての Blazor コンポーネントは、既定で ComponentBase から継承します。 ComponentBase を拡張する独自の基本クラスを作成し、それをコンポーネントに継承させることができます。
ほとんどのページで状態の読み込みとエラー処理が必要だとします。基本クラスを作成できます。
using Microsoft.AspNetCore.Components;
public abstract class PageBase : ComponentBase
{
protected bool IsLoading { get; set; } = true;
protected string? ErrorMessage { get; set; }
protected async Task LoadDataAsync(Func<Task> action)
{
try
{
IsLoading = true;
ErrorMessage = null;
await action();
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
}
finally
{
IsLoading = false;
StateHasChanged();
}
}
}
基本クラスの使用
どのページ コンポーネントでも、ComponentBase から継承する代わりに、PageBase から継承します。
@page "/users"
@inherits PageBase
@if (IsLoading)
{
<div class="spinner"></div>
}
else if (ErrorMessage is not null)
{
<div class="alert alert-danger">@ErrorMessage</div>
}
else
{
<ul>
@foreach (var user in users)
{
<li>@user.Name</li>
}
</ul>
}
@code {
private List<User> users = new();
protected override async Task OnInitializedAsync()
{
await LoadDataAsync(async () =>
{
users = await Http.GetFromJsonAsync<List<User>>("api/users");
});
}
}
@inherits PageBase ディレクティブが鍵です。これは、デフォルトの ComponentBase の代わりに基本クラスを使用するように Blazor に指示します。これで、IsLoading、ErrorMessage、および LoadDataAsync() を継承するすべてのページで無料で取得できるようになりました。
基本クラスにサービスを挿入する
基本クラスにサービスを挿入して、すべての子コンポーネントで利用できるようにすることもできます。
public abstract class PageBase : ComponentBase
{
[Inject]
protected NavigationManager Navigation { get; set; } = default!;
[Inject]
protected IToastService Toast { get; set; } = default!;
protected bool IsLoading { get; set; } = true;
protected string? ErrorMessage { get; set; }
protected void NavigateBack() => Navigation.NavigateTo("javascript:history.back()");
protected void ShowSuccess(string message) => Toast.ShowSuccess(message);
}
PageBase を継承するすべてのコンポーネントは、何も注入しなくても Navigation、Toast、NavigateBack()、および ShowSuccess() にアクセスできるようになりました。
汎用基本クラスをさらに深く掘り下げる
一般的な CRUD パターンの汎用基本クラスを作成することもできます。
public abstract class CrudPageBase<T> : PageBase
{
protected List<T> Items { get; set; } = new();
protected T? SelectedItem { get; set; }
protected abstract Task<List<T>> FetchItems();
protected abstract Task DeleteItem(T item);
protected override async Task OnInitializedAsync()
{
await LoadDataAsync(async () =>
{
Items = await FetchItems();
});
}
protected async Task OnDelete(T item)
{
await LoadDataAsync(async () =>
{
await DeleteItem(item);
Items = await FetchItems();
ShowSuccess("Item deleted.");
});
}
}
そうすれば、実際のページは非常にきれいになります。
@page "/products"
@inherits CrudPageBase<Product>
@* just the markup, all logic lives in the base class *@
@code {
protected override Task<List<Product>> FetchItems()
=> Http.GetFromJsonAsync<List<Product>>("api/products");
protected override Task DeleteItem(Product item)
=> Http.DeleteAsync($"api/products/{item.Id}");
}
使用する場合と使用しない場合
コンポーネントの継承は、状態の読み込み、エラー処理、認証チェック、CRUD パターンなどの共有動作に最適です。ただし、継承階層を深くしすぎないでください。2 レベル以上の階層を深くする場合は、(前の投稿の LoadingComponent アプローチのような) 合成を使用したほうがよいでしょう。
私は通常、ページの「タイプ」ごとに 1 つの基本クラスに保ちます。通常のページの場合は PageBase、フォームの場合は FormPageBase です。それだけです。
投稿が気に入っていただければ幸いです!ソーシャルメディアで**@emimontesdeoca**までお気軽にご連絡ください。