Наследование компонентов в Blazor

· 3 мин чтения

Я создавал проект, в котором было несколько страниц форм, и каждая из них имела одинаковую логику состояния загрузки, одинаковую обработку ошибок и одинаковые всплывающие уведомления. Копирование всего этого казалось неправильным, поэтому я изучил наследование компонентов в 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.

Ресурсы