Наследование компонентов в Blazor
Я создавал проект, в котором было несколько страниц форм, и каждая из них имела одинаковую логику состояния загрузки, одинаковую обработку ошибок и одинаковые всплывающие уведомления. Копирование всего этого казалось неправильным, поэтому я изучил наследование компонентов в Blazor. Оказывается, это довольно просто, поскольку компоненты Blazor — это всего лишь классы C#.
Основы
Каждый компонент Blazor по умолчанию наследует от ComponentBase . Вы можете создать свой собственный базовый класс, расширяющий ComponentBase, а затем наследовать от него свои компоненты.
Допустим, большинству наших страниц требуется состояние загрузки и обработка ошибок. Мы можем создать базовый класс:
[[[ТОК_2]]]
Использование базового класса
Теперь в любом компоненте страницы вместо наследования от 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() бесплатно на каждой странице, которая наследуется от него.
Внедрение сервисов в базовый класс
Вы также можете внедрить сервисы в базовый класс, чтобы они были доступны всем дочерним компонентам:
[[[ТОК_11]]]
Каждый компонент, который наследует от 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.