وراثة المكونات في Blazor

· 3 دقيقة قراءة

كنت أقوم بإنشاء مشروع يحتوي على مجموعة من صفحات النماذج، وكان لكل واحدة منها نفس منطق حالة التحميل، ونفس معالجة الأخطاء، ونفس إشعارات الخبز المحمص. بدا كل هذا خطأً في النسخ واللصق، لذلك بحثت في وراثة المكونات في 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 **.

الموارد