Blazor 中的继承组件
我正在构建一个具有一堆表单页面的项目,每个表单页面都有相同的加载状态逻辑、相同的错误处理和相同的 toast 通知。复制粘贴所有这些感觉都是错误的,因此我研究了 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 指令是关键。它告诉 Blazor 使用我们的基类而不是默认的 ComponentBase。现在,我们在继承它的每个页面中免费获得 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 模式。但不要过度使用深层继承层次结构 - 如果您发现自己的深度超过两层,那么组合可能会更好(就像上一篇文章中的 LoadingComponent 方法)。
我通常将其保留为每个“类型”页面的一个基类:PageBase 用于常规页面,FormPageBase 用于表单,仅此而已。
希望你喜欢这篇文章!请随时通过任何社交媒体与我联系:@emimontesdeoca。