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 지시문이 핵심입니다. 이는 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로 소셜 미디어를 통해 언제든지 저에게 연락해주세요.