طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 242 · أطر الويب
الدرس 2 · الوحدة 1: إتقان الهندسة المعتمدة على المكوّنات

المكوّنات والخصائص props

مكوّن واحد، وبيانات مختلفة في كل مرة. ⏱ نحو 30 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تمرر بيانات إلى المكوّن عبر props وتستقبلها بالتفكيك مع قيم افتراضية.
  • تبني مكوّنات «إطار» بالخاصية children.
  • تعرض القوائم بـ map مع key صحيح.
  • تعرض العناصر شرطيًّا بـ && و ?:.

١المكوّن كالدالة: يستقبل معطيات

المكوّن الذي يعرض دائمًا النص نفسه قليل الفائدة. وكما تستقبل الدالة معاملات، يستقبل المكوّن خصائص props. نمررها كسمات HTML، ويستقبلها المكوّن كائنًا واحدًا:

import Greeting from './Greeting.jsx'; export default function App() {  return (    <>      <Greeting name="سعود" city="المدينة المنورة" />      <Greeting name="نورة" city="الرياض" />      <Greeting name="خالد" city="جدة" />    </>  );}

مكوّن واحد كُتب مرة، واستُخدم ثلاث مرات ببيانات مختلفة. هذه إعادة الاستخدام التي تطلبها الخطة: كلما زادت المكوّنات المشتركة قل الكود المكرر.

٢التفكيك والقيم الافتراضية

بدل props.name في كل مكان، يفكّك المطوّرون الخصائص في سطر الدالة نفسه، ويعطون قيمة افتراضية لما قد لا يُمرَّر:

function Badge({ text, color = '#149ECA' }) {  return (    <span style={{ background: color, color: 'white', padding: '2px 10px', borderRadius: 99, marginInlineEnd: 6 }}>      {text}    </span>  );} export default function App() {  return (    <p>      <Badge text="جديد" />      <Badge text="خصم" color="crimson" />      <Badge text="شحن مجاني" color="green" />    </p>  );}
ما تمررهكيف
نصtitle="قهوة"
عدد أو منطقي أو أي تعبيرprice={35} inStock={true}
مصفوفة أو كائنtags={['عربي', 'محمّص']}
دالة (للأحداث، الدرس القادم)onBuy={handleBuy}

٣الخاصية children: مكوّن يغلّف غيره

ما تكتبه بين وسمي المكوّن يصله في خاصية خاصة اسمها children. هكذا نبني مكوّنات «إطار» نعيد استخدامها لأي محتوى:

function Panel({ title, children }) {  return (    <section style={{ border: '1px solid #ccd', borderRadius: 10, padding: '6px 12px', marginBottom: 8 }}>      <h3 style={{ margin: '4px 0', color: '#149ECA' }}>{title}</h3>      {children}    </section>  );} export default function App() {  return (    <>      <Panel title="الإعلانات">        <p>يبدأ التسجيل في الدورات الصيفية الأحد القادم.</p>      </Panel>      <Panel title="روابط مهمة">        <ul>          <li>الجدول الدراسي</li>          <li>نظام رايات</li>        </ul>      </Panel>    </>  );}

٤عرض القوائم: map و key

البيانات الحقيقية تأتي مصفوفات (من الخادم لاحقًا). نحوّل كل عنصر إلى مكوّن بالدالة map، ونعطي كل عنصر مفتاحًا key فريدًا ثابتًا، غالبًا رقمه في القاعدة:

import ProductCard from './ProductCard.jsx'; const products = [  { id: 1, name: 'قهوة عربية', price: 35 },  { id: 2, name: 'عسل سدر', price: 120 },  { id: 3, name: 'تمر سكري', price: 45 },]; export default function App() {  return (    <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>      {products.map(p => (        <ProductCard key={p.id} name={p.name} price={p.price} />      ))}    </div>  );}
✗ خطأ
export default function App() {  const cities = ['الرياض', 'جدة', 'أبها'];  return <ul>{cities.map(c => <li>{c}</li>)}</ul>;}
✓ صواب
export default function App() {  const cities = ['الرياض', 'جدة', 'أبها'];  return <ul>{cities.map(c => <li key={c}>{c}</li>)}</ul>;}

الصفحة تظهر في الحالتين، لكن React يحذّر في Console: بلا key لا يعرف أي عنصر تغيّر حين تتغير القائمة، فقد يخلط بين العناصر. استخدم معرّفًا ثابتًا لا رقم الترتيب في المصفوفة.

٥العرض الشرطي

كثيرًا ما نعرض شيئًا أو نخفيه حسب البيانات. لا نكتب if داخل JSX، بل نستخدم تعابير:

function Product({ name, stock }) {  return (    <li>      {name}{' '}      {stock === 0 && <b style={{ color: 'crimson' }}>نفد</b>}      {stock > 0 && stock < 5 && <small> (بقي {stock} فقط)</small>}    </li>  );} export default function App() {  const user = { name: 'ريم', isAdmin: false };  return (    <>      <p>{user.isAdmin ? 'لوحة المدير' : 'أهلًا بك يا ' + user.name}</p>      <ul>        <Product name="قهوة" stock={12} />        <Product name="عسل" stock={0} />        <Product name="تمر" stock={3} />      </ul>    </>  );}
الحالةالصياغة
اعرض أو لا تعرض{شرط && <عنصر/>}
اختر بين اثنين{شرط ? <أ/> : <ب/>}
منطق أطولاحسبه في متغير قبل return، ثم اعرض المتغير

٦صمّم بالمكوّنات

قبل أن تكتب صفحة، ارسم صناديق حول أجزائها، وسمِّ كل صندوق: هذا هو تقسيم المكوّنات. والقاعدة: مكوّن واحد لمسؤولية واحدة، ويُستخرج ما يتكرر في مكوّن مشترك.

أسأل نفسيإذا كانت الإجابة نعم
هل يتكرر هذا الجزء؟اجعله مكوّنًا وأعطه props
هل الملف صار طويلًا (أكثر من ~100 سطر)؟قسّمه إلى مكوّنات أصغر
هل يحتاج الجزء بيانات مختلفة كل مرة؟مرّرها props لا تكتبها داخله
هل هو إطار لمحتوى متغير؟استخدم children
🧪 مختبر Reactأضف دورة ثالثة إلى المصفوفة، وأضف للمكوّن Course خاصية جديدة. لاحظ {...c}: تمرر كل خصائص الكائن مرة واحدة.

توقّع وتحقّق

كيف يصل محتوى <Panel>…</Panel> إلى المكوّن Panel؟

children خاصية خاصة.

أفضل قيمة للخاصية key:

ثابت وفريد.

اتجاه تدفق البيانات بالخصائص:

اتجاه واحد.

{stock === 0 && <b>نفد</b>} تعرض «نفد» حين:

الشرط ثم العنصر.

الخلاصة

تحدٍّ برمجي: كتالوج المنتجات

متوسط

اعرض كتالوج منتجات بمكوّن واحد يُعاد استخدامه لكل منتج.

المطلوب:
  • المصفوفة products جاهزة في الكود، فلا تغيّرها.
  • اكتب مكوّن ProductCard يستقبل name و price و stock ويعرض <div className="card"> فيه <h3> بالاسم و <p className="price">35 ريال</p>.
  • إن كان المخزون صفرًا فأضف داخل البطاقة <span className="out">نفد</span>، وإلا فلا يظهر.
  • اعرض البطاقات بـ map مع key مناسب، ولا تحذيرات في Console.
👀 اعرض الناتج المطلوب (وجرّبه)
✓ التحقق الآلي من حلّك:
  • الكود يعمل بلا أخطاء في Console
  • أربع بطاقات div.card
  • أسماء المنتجات في h3 بالترتيب
  • السعر بالشكل «35 ريال»
  • «نفد» في البطاقتين اللتين مخزونهما صفر فقط

💡 تلميح 1
داخل App: {products.map(p => <ProductCard key={p.id} … />)}
💡 تلميح 2
العرض الشرطي: {stock === 0 && <span className="out">نفد</span>}

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

const products = [  { id: 1, name: 'قهوة عربية', price: 35, stock: 12 },  { id: 2, name: 'عسل سدر', price: 120, stock: 0 },  { id: 3, name: 'تمر سكري', price: 45, stock: 30 },  { id: 4, name: 'شاي أخضر', price: 18, stock: 0 },]; function ProductCard({ name, price, stock }) {  return (    <div className="card">      <h3>{name}</h3>      <p className="price">{price} ريال</p>      {stock === 0 && <span className="out">نفد</span>}    </div>  );} export default function App() {  return (    <div>      {products.map(p => (        <ProductCard key={p.id} name={p.name} price={p.price} stock={p.stock} />      ))}    </div>  );}
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس