Blazor من الصفر: الفصل 3 — مكوّنات قابلة للتوسّع

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

مرحبًا بك في الفصل 3 من Blazor من الصفر. إذا فاتك الفصل 2، ابدأ به أولًا حتى يكون مشروعك الأساسي جاهزًا.

في الفصل 2 جعلنا التطبيق يعمل. في هذا الفصل سنجعل الكود قابلاً للصيانة.

مشاريع Blazor تصبح فوضوية بسرعة عندما تتحول كل صفحة إلى ملف .razor ضخم. المكوّنات هي الحل: اتساق أعلى، إعادة استخدام أفضل، وحدود أوضح بين أجزاء الواجهة.


ما هو مكوّن Blazor فعليًا؟

المكوّن هو ملف .razor يستطيع:

  • عرض الـ markup
  • حفظ حالة محلية
  • استقبال مدخلات عبر Parameters
  • إرسال أحداث إلى المكوّن الأب
  • عرض محتوى فرعي

أثناء التشغيل، يتعامل Blazor مع كل مكوّن كآلة حالة صغيرة. عند تغيّر الحالة يعيد التصيير ويطبّق DOM diff.

لذلك الهدف هو تصميم مدخلات واضحة وسلوك متوقّع.


الخطوة 1: ابدأ بمكوّن صغير ومحدد

أنشئ Components/Common/SectionHeader.razor:

<header class="section-header">
    <h2>@Title</h2>
    @if (!string.IsNullOrWhiteSpace(Subtitle))
    {
        <p>@Subtitle</p>
    }
</header>

@code {
    [Parameter] public string Title { get; set; } = string.Empty;
    [Parameter] public string? Subtitle { get; set; }
}

استخدمه في Components/Pages/Home.razor:

@page "/"

<PageTitle>Blazor من الصفر</PageTitle>

<SectionHeader
    Title="Blazor من الصفر"
    Subtitle="الفصل 3 عن المكوّنات." />

المثال بسيط، لكن الفكرة محورية: يجب أن يُفهم المكوّن من خلال معاملاته.


الخطوة 2: اجعل السلوك صريحًا بالمعاملات

لننشئ زرًا قابلاً لإعادة الاستخدام.

Components/Common/AppButton.razor:

<button class="app-button @VariantCssClass" @onclick="OnClick">
    @Text
</button>

@code {
    [Parameter] public string Text { get; set; } = "Button";
    [Parameter] public string Variant { get; set; } = "primary";
    [Parameter] public EventCallback OnClick { get; set; }

    private string VariantCssClass => Variant.ToLowerInvariant() switch
    {
        "secondary" => "app-button--secondary",
        "danger" => "app-button--danger",
        _ => "app-button--primary"
    };
}

الاستخدام:

@code {
    private int _savedCount;

    private void Save()
    {
        _savedCount++;
    }
}

<AppButton Text="Save" Variant="primary" OnClick="Save" />
<p>تم الحفظ @_savedCount مرة.</p>

المهم هنا هو تصميم العقد:

  • معاملات قليلة وواضحة
  • أسماء صريحة بدل سلوك “سحري”
  • قيم افتراضية آمنة

الخطوة 3: استخدم RenderFragment للتركيب

RenderFragment يسمح للمكوّن الأب بتمرير أجزاء واجهة إلى المكوّن الابن.

أنشئ Components/Common/Card.razor:

<article class="card">
    <header class="card__header">@Title</header>
    <section class="card__body">
        @ChildContent
    </section>
</article>

@code {
    [Parameter] public string Title { get; set; } = string.Empty;
    [Parameter] public RenderFragment? ChildContent { get; set; }
}

الاستخدام:

<Card Title="Roadmap">
    <ul>
        <li>المكوّنات</li>
        <li>Data binding</li>
        <li>Routing</li>
    </ul>
</Card>

هذا النمط يساعدك على بناء صفحات متناسقة بدون تكرار نفس wrapper markup.


الخطوة 4: فضّل التركيب على الصفحات العملاقة

عندما تكبر الصفحة، قسّمها حسب المسؤولية:

  • ProfileSummary لكتلة التعريف
  • ProfileStats للمؤشرات
  • ProfileActivityList للنشاط الأخير

بهذا تصبح الصفحة طبقة تنسيق، وليست مكانًا لكل التفاصيل.


الخطوة 5: وازن بين الـ markup والمنطق

في المكوّنات البسيطة، @code داخل الملف كافٍ.

في المكوّنات الكبيرة، انقل المنطق إلى code-behind:

  • UserCard.razor
  • UserCard.razor.cs

هذا يجعل كلًا من الواجهة ومنطق C# أسهل قراءة.


الخطوة 6: بنية مجلدات عملية

بنية فعالة مع النمو:

  • Components/Pages/ -> صفحات قابلة للتوجيه
  • Components/Layout/ -> هيكل التطبيق والتنقّل
  • Components/Common/ -> عناصر عامة مشتركة
  • Components/Features/<FeatureName>/ -> مكوّنات خاصة بكل ميزة

أخطاء شائعة في بدايات مشاريع Blazor

  • تمرير عدد كبير من المعاملات بدل نموذج مخصص
  • وضع قواعد العمل داخل مكوّنات الصفحات
  • إنشاء “مكوّن إلهي” بعدد هائل من الأسطر
  • تكرار markup بدل استخراج مكوّنات صغيرة قابلة لإعادة الاستخدام

الفصل القادم

في الفصل 4 سنركّز على Data binding والأحداث: @bind، التعامل مع الأحداث، مفاضلات two-way binding، وأنماط تبقي الحالة متوقعة.