Herdando componentes no Blazor
Eu estava construindo um projeto que tinha várias páginas de formulário e cada uma delas tinha a mesma lógica de estado de carregamento, o mesmo tratamento de erros e as mesmas notificações do sistema. Copiar e colar tudo isso parecia errado, então examinei a herança de componentes no Blazor. Acontece que é bastante simples, já que os componentes do Blazor são apenas classes C#.
O básico
Cada componente Blazor herda de ComponentBase por padrão. Você pode criar sua própria classe base que estende ComponentBase e então fazer com que seus componentes sejam herdados dela.
Digamos que a maioria de nossas páginas precise de estado de carregamento e tratamento de erros. Podemos criar uma classe base:
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();
}
}
}
#Usando a classe base
Agora, em qualquer componente de página, em vez de herdar de ComponentBase, herdamos de nosso 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");
});
}
}
A diretiva @inherits PageBase é a chave. Diz ao Blazor para usar nossa classe base em vez do padrão ComponentBase. Agora temos IsLoading, ErrorMessage e LoadDataAsync() gratuitamente em todas as páginas herdadas dele.
Injetando serviços na classe base
Você também pode injetar serviços na classe base para que estejam disponíveis para todos os componentes filhos:
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);
}
Cada componente que herda de PageBase agora tem acesso a Navigation, Toast, NavigateBack() e ShowSuccess() sem precisar injetar nada.
Indo mais fundo com classes base genéricas
Você pode até criar classes base genéricas para padrões CRUD comuns:
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.");
});
}
}
Então sua página real fica super limpa:
@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}");
}
Quando usar e quando não usar
A herança de componentes é ótima para comportamento compartilhado, como estados de carregamento, tratamento de erros, verificações de autenticação ou padrões CRUD. Mas não exagere com hierarquias de herança profundas - se você estiver indo mais de dois níveis de profundidade, provavelmente estará melhor com a composição (como a abordagem LoadingComponent de uma postagem anterior).
Eu normalmente mantenho uma classe base por “tipo” de página: PageBase para páginas normais, FormPageBase para formulários, e isso é tudo.
Espero que você tenha gostado da postagem! Sinta-se à vontade para entrar em contato comigo em qualquer mídia social em @emimontesdeoca.