المكوّنات والخصائص 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="جدة" /> </> );}
export default function Greeting(props) { return ( <p> أهلًا يا <b>{props.name}</b> من {props.city} </p> );}
مكوّن واحد كُتب مرة، واستُخدم ثلاث مرات ببيانات مختلفة. هذه إعادة الاستخدام التي تطلبها الخطة: كلما زادت المكوّنات المشتركة قل الكود المكرر.
٢التفكيك والقيم الافتراضية
بدل 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 ProductCard({ name, price }) { return ( <div style={{ border: '1px solid #ccd', borderRadius: 10, padding: 10, minWidth: 120 }}> <b>{name}</b> <p style={{ margin: 0 }}>{price} ريال</p> </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 |
{...c}: تمرر كل خصائص الكائن مرة واحدة.توقّع وتحقّق
كيف يصل محتوى <Panel>…</Panel> إلى المكوّن Panel؟
children خاصية خاصة.أفضل قيمة للخاصية key:
اتجاه تدفق البيانات بالخصائص:
{stock === 0 && <b>نفد</b>} تعرض «نفد» حين:
الخلاصة
propsمعطيات المكوّن: نمررها كسمات، وتصل كائنًا واحدًا، وهي للقراءة فقط.function Card({ title, color = "blue" }): تفكيك وقيمة افتراضية.children: ما بين وسمي المكوّن.- القوائم:
array.map(x => <Item key={x.id} … />)وkeyثابت فريد. - الشرط:
{cond && <A/>}أو{cond ? <A/> : <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
{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> );}