الوحدة 2 · الـ Components والـ Props
أنماط التركيب (Composition)
19 دقراءة 4 دبيئة تجريب الكود
الدرس في لمحة
ابنِ components مرنة باستخدام الـ prop المسمّى children وفتحات JSX، وخصّص الـ components العامة، ومرّر العناصر إلى الأسفل لتتجنّب تمرير الـ props عبر طبقات لا تحتاجها.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
سيحتوي Watchlist على عدّة صناديق متشابهة: لوحة «To watch»، ولوحة «Watched»، وربما لوحة «Recommendations» لاحقًا. لكل منها شريط عنوان، وأحيانًا زر أو اثنان على الجانب، ومحتوى مختلف في الداخل. يمكنك إضافة prop لكل نوع محتمل من المحتوى. أو يمكنك أن تدع المستدعي يضع ما يشاء في الداخل، كما تضع أي شيء داخل <div>.
هذا النهج الثاني هو التركيب (composition)، وبه تُبنى أغلب الـ components المرنة في React.
الـ prop المسمّى children
كل ما تضعه بين وسمي component يصل إليه كـ prop اسمه children:
export default function Panel({ title, children }) {
return (
<section className="panel">
<h2>{title}</h2>
<div className="panel-body">{children}</div>
</section>
);
}<Panel title="To watch">
<MovieList movies={toWatch} />
</Panel>
<Panel title="Watched">
<p>You've watched {watched.length} movies.</p>
</Panel>لا يعرف Panel ما في الداخل ولا يهمّه. هو يملك الإطار (الـ section والعنوان والتنسيق)، والمستدعي يملك المحتوى. لقد استخدمت هذا طوال الوقت من دون أن تلاحظ: <main> و<section> يأخذان children أيضًا.
يمكن لـ children أن يكون أي شيء قابل للعرض: عنصرًا واحدًا، أو عدّة عناصر، أو نصًا، أو لا شيء. تعامل معه كصندوق مغلق واعرضه في مكانه. لا تفحصه ولا تعدّه ولا تعدّله؛ وإذا احتاج الغلاف إلى معرفة شيء عن محتواه، فيجب أن تصله تلك المعلومة كـ prop عادي.
تظهر الفائدة حين يتغيّر التصميم. إذا حصلت اللوحات الشهر القادم على زوايا دائرية وظل، فستعدّل Panel مرة واحدة وتتحدّث كل لوحة في التطبيق، مهما كان محتواها.
أكثر من slot واحد
الـ children هو slot (فتحة) واحد. وحين يحتاج الـ component إلى محتوى في عدّة أماكن، استخدم props عادية تحمل JSX. هذه لوحة فيها أزرار اختيارية في ترويستها:
export default function Panel({ title, actions, children }) {
return (
<section className="panel">
<header className="panel-header">
<h2>{title}</h2>
{actions}
</header>
<div className="panel-body">{children}</div>
</section>
);
}<Panel
title="To watch"
actions={<button onClick={shuffle}>Shuffle</button>}
>
<MovieList movies={toWatch} />
</Panel>إذا لم يُمرَّر actions فقيمته undefined، وهي لا تعرض شيئًا، فيصبح الـ slot اختياريًا بلا أي جهد. قارن هذا بواجهة مثل buttons={['Shuffle', 'Clear']}: سيحتاج Panel إلى إنشاء الأزرار، وربط المعالجات، والتعامل مع الأيقونات وحالات التعطيل، وإضافة prop جديد مع كل طلب. أمّا مع الـ slot فيقرّر Panel أين توضع الأشياء، ويقرّر المستدعي ما هي.
التخصيص
أحيانًا تريد نسخة ثابتة من component عام. لا تنسخه؛ بل ارسمه مع props مضبوطة مسبقًا:
export function Button({ variant = 'neutral', children, ...rest }) {
return (
<button className={`btn btn-${variant}`} {...rest}>
{children}
</button>
);
}
export function DangerButton(props) {
return <Button variant="danger" {...props} />;
}الـ component المسمّى DangerButton هو Button اتُّخذ فيه قرار واحد مسبقًا. والـ ...rest في Button يمرّر كل ما عدا ذلك، مثل onClick أو type أو disabled، إلى الـ <button> الحقيقي، فيستخدمه المستدعون كعنصر HTML الذي يعرفونه أصلًا. أصلِح خطأً في Button وستحصل كل الأشكال المشتقّة منه على الإصلاح.
التركيب بدلًا من الـ prop drilling
هذه مشكلة يصادفها كل تطبيق. يملك App الأفلام. ويرسم Layout الـ component المسمّى Sidebar، والشريط الجانبي يعرض WatchCount. لا يحتاج إلى الأفلام إلا WatchCount، لكن لتوصيلها إليه تمرّرها عبر كل مستوى:
<Layout movies={movies} /> // Layout doesn't use movies…
function Layout({ movies }) {
return <Sidebar movies={movies} />; // …neither does Sidebar
}تمرير البيانات عبر components لا تفعل سوى إعادة تمريرها يُسمّى prop drilling (حفر الـ props عبر الطبقات). إنه مملّ، وكل component وسيط صار يعتمد على بيانات لا يستخدمها.
والتركيب يزيله غالبًا. دع App، الذي يملك البيانات، يُنشئ العنصر، ودع Layout يضعه في مكانه:
export default function App() {
return (
<Layout sidebar={<WatchCount movies={movies} />}>
<MovieList movies={movies} />
</Layout>
);
}
function Layout({ sidebar, children }) {
return (
<div className="layout">
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}لم يعد Layout يعرف أن الأفلام موجودة. صار component تخطيط خالصًا يمكنك إعادة استخدامه في أي صفحة. وتوفّر React أيضًا الـ context للبيانات التي تحتاجها components كثيرة متباعدة، مثل السمة الحالية أو المستخدم المسجّل دخوله. لكن توثيق React يوصي بتجربة التركيب أولًا، وهو ينجح في الغالب.
في الدرس التالي تجعل الـ components تعرض أشياء مختلفة بحسب البيانات، من شارة «Watched» إلى رسالة القائمة الفارغة، باستخدام العرض الشرطي.
أهم النقاط
- كل ما تضعه بين وسمي الفتح والإغلاق لـ component يصل إليه في الـ prop المسمّى
children. - يمكن للـ prop أن يحمل JSX، فيستطيع الـ component أن يوفّر عدّة slots مسمّاة مثل
actionsأوfooter. - خصّص الـ component العام برسمه مع props مضبوطة مسبقًا بدلًا من نسخه.
- تمرير عناصر جاهزة إلى الأسفل يغنيك غالبًا عن تمرير البيانات عبر components لا تستخدمها.