الوحدة 3 · المكوّنات
الـ Component API: الـ Props والـ Variants والـ Slots
22 دقراءة 4 دبيئة تجريب الكود
الدرس في لمحة
صمّم component API يستخدم variants من نوع enum بدل الأعلام المنطقية، ومفردات props موحّدة، وتمريرًا للسمات الأصلية، وslots للمحتوى، مع Button الخاص بـ Northwind مثالًا.
- 1الـ enums للاختيارات، والقيم المنطقية للحالات
- 2مفردات واحدة عبر النظام
- 3لا تحجب المنصّة
- 4الـ slots للمحتوى
- 5الـ variants في CSS: حوّل الـ props إلى سمات
- 6راجع الـ API قبل أن تبنيه
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
كان لـ Button الذي أطلقته Northwind عام 2021 تسعة props: primary، secondary، danger، small، large، outline، block، icon، وonPress. والقيم المنطقية (boolean) السبع منها وحدها تسمح بـ 128 تركيبة، ووجدت الفرق معظمها. كان في التطبيق الإنتاجي <Button primary danger small large>. كان يعرض شيئًا ما، ولم يستطع أحد أن يقول ماذا كان يُفترض أن يكون. الـ component API عقدٌ مع كل فريق يستخدم المكوّن، وكأي عقد، الجزء الصعب هو ما يسمح به.
الـ enums للاختيارات، والقيم المنطقية للحالات
القاعدة الأولى تخصّ شكل الـ props. إن كانت الخيارات متنافية، فمكانها prop واحد من نوع enum. وإن كان الشيء يعمل أو لا يعمل، فهو قيمة منطقية (boolean).
type ButtonProps = React.ComponentProps<'button'> & {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
startIcon?: React.ReactNode;
endIcon?: React.ReactNode;
};حين يكون variant من نوع enum، لا يمكن أن يكون primary وdanger صحيحين معًا؛ ونظام الأنواع يرفض ذلك قبل مراجعة الكود. أما loading فيبقى قيمة منطقية، لأن الزر إما في حالة تحميل أو لا، بشكل مستقل عن الـ variant الخاص به.
مفردات واحدة عبر النظام
القاعدة الثانية هي الاتّساق بين المكوّنات. إن استخدم Button القيمة size="sm"، فكذلك يفعل Input وSelect وTabs وBadge، بالقيم الثلاث نفسها. وإن سمّى أحد المكوّنات معالج التغيير onChange ومرّر إليه القيمة الجديدة، فكلها تفعل ذلك. تحتفظ Northwind بمسرد API من صفحة واحدة لأسماء الـ props المشتركة، والمكوّنات الجديدة تعيد استخدام هذه الأسماء ما لم يوجد سبب قوي للخروج عنها.
وهذا أهمّ من أي قرار منفرد بشأن prop. المهندس الذي استخدم ثلاثة مكوّنات من Northwind يجب أن يستطيع تخمين API المكوّن الرابع بشكل صحيح. وكل تناقض ضريبة صغيرة يدفعها الجميع، كل يوم.
لا تحجب المنصّة
القاعدة الثالثة: يجب ألا يسلب المكوّن أبدًا ما يقدّمه HTML أصلًا. الـ Button هو <button>، لذا يجب أن يقبل كل ما يقبله <button>، بما في ذلك سمات aria-*، وأن يمرّره:
export function Button({
variant = 'secondary',
size = 'md',
loading = false,
startIcon,
endIcon,
type = 'button',
children,
...rest
}: ButtonProps) {
return (
<button
{...rest}
type={type}
className={['nw-button', rest.className].filter(Boolean).join(' ')}
data-variant={variant}
data-size={size}
aria-busy={loading || undefined}
>
{startIcon}
<span className="nw-button__label">{children}</span>
{endIcon}
</button>
);
}لأن ref صار prop عاديًا في مكوّنات الدوال في React 19، فإنه يمرّ عبر ...rest مع كل شيء آخر، فتعمل إدارة التركيز (focus) والـ tooltips دون تغليف إضافي. وللسبب نفسه تعمل onClick وdisabled وform وaria-describedby.
الـ slots للمحتوى
الـ slot (الفتحة) مكان يمرّر فيه المستخدم محتوى بدل الإعدادات. children هو الـ slot الرئيسي؛ وstartIcon وendIcon هما slots مسمّاة. قارن بين طريقتين لإضافة أيقونة:
<Button icon="truck" iconPosition="left">Assign</Button> // configuration
<Button startIcon={<TruckIcon />}>Assign</Button> // slotنسخة الإعدادات تحتاج أن يعرف الـ Button كل اسم أيقونة وكل موضع، ويُضاف إليها prop كلما أراد أحد شيئًا جديدًا. أما نسخة الـ slot فتقبل أي عنصر، فتستطيع الفرق تمرير مجموعة أيقونات مختلفة أو صورة رمزية صغيرة دون إصدار جديد. والدرس التالي يأخذ هذه الفكرة أبعد بكثير.
الـ variants في CSS: حوّل الـ props إلى سمات
يكتب الـ Button قيم الـ props الخاصة به في السمتين data-variant وdata-size، فيعكس الـ CSS الـ API واحدًا لواحد. وكل variant يغيّر الـ component tokens وحدها:
.nw-button[data-variant="primary"] {
--nw-button-bg: var(--nw-color-action-bg);
--nw-button-text: var(--nw-color-action-text);
--nw-button-border: transparent;
}الـ data attributes تُبقي نقاط ربط الأنماط مقروءة في DevTools، حيث ترى data-variant="primary" بدل اسم class مولَّد بالـ hash لا يُقرأ، ولا يمكن جمعها في تركيبة عبثية كما يمكن مع classes مثل .primary.danger.
راجع الـ API قبل أن تبنيه
تكتب Northwind كود الاستخدام قبل كود المكوّن. كل اقتراح مكوّن جديد يبدأ بثلاثة مقاطع: الاستخدام الأكثر شيوعًا، والاستخدام الأعقد الذي طلبه أحد، واستخدام واحد يجب أن يكون مستحيلًا. ثم تطرح المراجعة أربعة أسئلة. هل تطابق أسماء الـ props مسرد الـ API؟ هل يمكن لأي تركيبة من الـ props أن تصف شيئًا بلا معنى؟ هل يمرّر المكوّن كل ما يدعمه عنصره الأصلي؟ وما الذي سيحتاجه الفريقان الثاني والثالث اللذان سيستخدمانه ولم يحتجه الفريق الأول؟
تستغرق هذه المراجعة نحو ثلاثين دقيقة. أما تغيير الـ API بعد أن يعتمد عليه عشرون فريقًا فيتطلّب إصدارًا رئيسيًا (major)، ودليل انتقال، وشهورًا من المتابعة، وهذا موضوع القسم 4.
في التمرين تكتب قواعد الـ variant والحجم لهذا الـ Button باستخدام الـ component tokens وحدها. وبعدها ترى ما يحدث حين يتجاوز المكوّن حدود الـ props كليًا.
أهم النقاط
- استخدم prop واحدًا من نوع enum مثل
variantللخيارات المتنافية؛ فالقيم المنطقية المنفصلة تسمح بتركيبات لا معنى لها. - اجعل أسماء الـ props وقيمها متطابقة عبر المكوّنات، حتى يعني
size="sm"الشيء نفسه على Button وInput وSelect. - مرّر السمات الأصلية والـ
refإلى العنصر الأساسي، حتى لا يحجب المكوّن أبدًا ما يقدّمه HTML أصلًا. - القيم الافتراضية قرارات تصميم؛ اختر الخيار الآمن والشائع، مثل
type="button"والـ variant الثانوي.