أساسيات React.js

المكونات: المكونات الوظيفية

18 دقيقة الدرس 4 من 40

ما هي مكونات React؟

المكونات هي اللبنات الأساسية لتطبيقات React. فكر فيها على أنها قطع مخصصة وقابلة لإعادة الاستخدام من واجهة المستخدم يمكنك استخدامها في جميع أنحاء تطبيقك. تماماً مثل الدوال في JavaScript التي تقسم المنطق المعقد إلى قطع أصغر، تقسم المكونات واجهات المستخدم المعقدة إلى أجزاء قابلة للإدارة ومستقلة.

تعريف المكون: مكون React هو دالة JavaScript (أو فئة) تُرجع JSX تصف ما يجب أن يظهر على الشاشة. يمكن للمكونات قبول مدخلات (تسمى "props") والحفاظ على حالتها الداخلية الخاصة.

هناك نوعان من المكونات في React:

  • المكونات الوظيفية: دوال JavaScript تُرجع JSX (النهج الحديث، محور هذا الدرس)
  • مكونات الفئة: فئات ES6 التي تمتد React.Component (النهج القديم، لا يزال يُستخدم في قواعد الكود القديمة)

تطوير React الحديث يستخدم بشكل أساسي المكونات الوظيفية مع Hooks. يركز هذا الدرس حصرياً على المكونات الوظيفية.

إنشاء أول مكون وظيفي

المكون الوظيفي هو ببساطة دالة JavaScript تُرجع JSX. إليك أبسط مكون ممكن:

// مكون وظيفي أساسي function Welcome() { return <h1>مرحباً بالعالم!</h1>; } // استخدام دالة سهمية (صيغة بديلة) const Welcome = () => { return <h1>مرحباً بالعالم!</h1>; }; // إرجاع ضمني مع دالة سهمية (لـ JSX بسيط) const Welcome = () => <h1>مرحباً بالعالم!</h1>;
اتفاقية التسمية: يجب أن تبدأ أسماء المكونات بحرف كبير. هذا يخبر React أن <Welcome /> هو مكون، وليس وسم HTML. استخدم PascalCase لأسماء المكونات: UserProfile، NavigationBar، ProductCard.

لننشئ مكوناً أكثر واقعية:

function UserCard() { const name = 'سارة أحمد'; const role = 'مطورة واجهات أمامية'; const avatar = 'https://via.placeholder.com/150'; return ( <div className="user-card"> <img src={avatar} alt={name} /> <h2>{name}</h2> <p>{role}</p> <button>عرض الملف الشخصي</button> </div> ); }

استخدام المكونات

بمجرد تعريف مكون، يمكنك استخدامه تماماً مثل عنصر HTML في JSX:

function App() { return ( <div className="App"> <UserCard /> <UserCard /> <UserCard /> </div> ); }

سيعرض هذا ثلاث بطاقات مستخدم متطابقة. لكن انتظر - ماذا لو أردنا أن تعرض كل بطاقة معلومات مختلفة؟ هنا تأتي الخصائص (props) (سنغطي أساسيات الخصائص قريباً)!

صيغة المكون: يمكن كتابة المكونات كوسوم ذاتية الإغلاق <UserCard /> أو مع وسوم فتح وإغلاق <UserCard></UserCard>. للمكونات بدون عناصر فرعية، يُفضل الإغلاق الذاتي.

تركيب المكونات

إحدى أقوى ميزات React هي تركيب المكونات - بناء واجهات مستخدم معقدة من خلال دمج مكونات أصغر:

// مكونات صغيرة ومركزة function Avatar({ src, alt }) { return <img src={src} alt={alt} className="avatar" />; } function UserName({ name }) { return <h2 className="user-name">{name}</h2>; } function UserRole({ role }) { return <p className="user-role">{role}</p>; } // مكون مركّب يستخدم مكونات أصغر function UserCard({ name, role, avatar }) { return ( <div className="user-card"> <Avatar src={avatar} alt={name} /> <UserName name={name} /> <UserRole role={role} /> <button>عرض الملف الشخصي</button> </div> ); }

فوائد تركيب المكونات:

  • إعادة الاستخدام: يمكن استخدام مكون Avatar في أي مكان تحتاج فيه لعرض صورة المستخدم
  • سهولة الصيانة: التغييرات على تصميم الصورة الرمزية تحدث في مكان واحد فقط
  • قابلية الاختبار: المكونات الصغيرة أسهل في الاختبار بشكل منفصل
  • قابلية القراءة: المكون المركب يُظهر بوضوح بنيته

مقدمة للخصائص (Props)

Props (اختصار لـ "properties") هي كيفية تمرير البيانات من مكون أصلي إلى مكون فرعي. إنها تجعل المكونات ديناميكية وقابلة لإعادة الاستخدام.

// مكون يقبل props function Greeting(props) { return <h1>مرحباً، {props.name}!</h1>; } // استخدام المكون مع props مختلفة function App() { return ( <div> <Greeting name="علي" /> <Greeting name="فاطمة" /> <Greeting name="محمد" /> </div> ); }

سيكون الناتج ثلاث تحيات مختلفة: "مرحباً، علي!"، "مرحباً، فاطمة!"، و"مرحباً، محمد!"

Props للقراءة فقط: يجب ألا يعدل المكون أبداً خصائصه الخاصة. تتدفق Props في اتجاه واحد: من الأصل إلى الفرع. يُسمى هذا "تدفق البيانات أحادي الاتجاه" وهو يجعل تطبيقات React يمكن التنبؤ بها وأسهل في تصحيح الأخطاء.

تفكيك الخصائص

بدلاً من الوصول إلى الخصائص باستخدام props.name، يمكنك تفكيكها للحصول على كود أنظف:

// بدون تفكيك function Greeting(props) { return <h1>مرحباً، {props.name}!</h1>; } // مع التفكيك (مُفضل) function Greeting({ name }) { return <h1>مرحباً، {name}!</h1>; } // خصائص متعددة function UserCard({ name, role, avatar, isOnline }) { return ( <div className="user-card"> <img src={avatar} alt={name} /> <h2>{name}</h2> <p>{role}</p> {isOnline && <span className="status">متصل</span>} </div> ); }

التفكيك يجعل كود المكون أكثر إيجازاً وأسهل في القراءة. إنه النهج المفضل في تطوير React الحديث.

الخصائص الافتراضية

يمكنك توفير قيم افتراضية للخصائص في حالة عدم تمريرها من الأصل:

// الطريقة 1: المعاملات الافتراضية (ES6) function Button({ text = 'انقر هنا', type = 'button' }) { return <button type={type}>{text}</button>; } // الطريقة 2: استخدام عامل OR المنطقي function Avatar({ src, size }) { const avatarSize = size || 100; return ( <img src={src} width={avatarSize} height={avatarSize} alt="صورة المستخدم" /> ); } // الاستخدام function App() { return ( <div> <Button /> {/* يستخدم الافتراضي "انقر هنا" */} <Button text="إرسال" /> {/* يستخدم "إرسال" */} <Avatar src="photo.jpg" /> {/* يستخدم الحجم الافتراضي 100 */} <Avatar src="photo.jpg" size={200} /> {/* يستخدم الحجم 200 */} </div> ); }
المعاملات الافتراضية مقابل defaultProps: للمكونات الوظيفية، استخدم معاملات ES6 الافتراضية في توقيع الدالة. صيغة Component.defaultProps الأقدم هي أساساً لمكونات الفئة (على الرغم من أنها تعمل أيضاً مع المكونات الوظيفية).

تنظيم المكونات وهيكل الملفات

مع نمو تطبيقك، يصبح تنظيم المكونات أمراً بالغ الأهمية:

الخيار 1: ملف واحد لكل مكون src/ ├── components/ │ ├── Button.js │ ├── UserCard.js │ ├── Avatar.js │ └── Navigation.js └── App.js الخيار 2: مجلدات المكونات src/ ├── components/ │ ├── Button/ │ │ ├── Button.js │ │ ├── Button.css │ │ └── Button.test.js │ ├── UserCard/ │ │ ├── UserCard.js │ │ ├── UserCard.css │ │ └── index.js │ └── Avatar/ │ ├── Avatar.js │ └── Avatar.css └── App.js

أفضل الممارسات لملفات المكونات:

// UserCard.js - هيكل ملف المكون // 1. الاستيرادات في الأعلى import React from 'react'; import './UserCard.css'; import Avatar from './Avatar'; // 2. تعريف المكون function UserCard({ name, role, avatar, onViewProfile }) { // 3. منطق المكون (إن وجد) const handleClick = () => { console.log(`عرض الملف الشخصي: ${name}`); onViewProfile(name); }; // 4. إرجاع JSX return ( <div className="user-card"> <Avatar src={avatar} alt={name} /> <h2>{name}</h2> <p>{role}</p> <button onClick={handleClick}>عرض الملف الشخصي</button> </div> ); } // 5. التصدير في الأسفل export default UserCard;

استيراد وتصدير المكونات

التصدير المسمى:

// Components.js - مكونات متعددة في ملف واحد export function Button({ text }) { return <button>{text}</button>; } export function Input({ placeholder }) { return <input placeholder={placeholder} />; } // استيراد التصدير المسمى import { Button, Input } from './Components';

التصدير الافتراضي:

// UserCard.js function UserCard({ name, role }) { return ( <div> <h2>{name}</h2> <p>{role}</p> </div> ); } export default UserCard; // استيراد التصدير الافتراضي (يمكن إعادة التسمية أثناء الاستيراد) import UserCard from './UserCard'; import Card from './UserCard'; // صالح: يمكن استخدام أي اسم
تصدير افتراضي واحد لكل ملف: يمكن أن يحتوي كل ملف على تصدير افتراضي واحد فقط، ولكن يمكن أن يحتوي على تصديرات مسماة متعددة. استخدم التصدير الافتراضي للمكون الرئيسي للملف، والتصدير المسمى للمكونات المساعدة أو الأدوات المساعدة.

أمثلة عملية للمكونات

مثال 1: مكون بطاقة المنتج

function ProductCard({ name, price, image, inStock, onAddToCart }) { return ( <div className="product-card"> <img src={image} alt={name} /> <h3>{name}</h3> <p className="price">{price.toFixed(2)} ريال</p> {inStock ? ( <button onClick={onAddToCart}>أضف إلى السلة</button> ) : ( <p className="out-of-stock">غير متوفر</p> )} </div> ); } // الاستخدام function Shop() { const handleAddToCart = (productName) => { console.log(`تمت إضافة ${productName} إلى السلة`); }; return ( <div className="shop"> <ProductCard name="سماعات لاسلكية" price={299.99} image="headphones.jpg" inStock={true} onAddToCart={() => handleAddToCart('سماعات لاسلكية')} /> </div> ); }

مثال 2: مكون تنبيه مع أنواع

function Alert({ type = 'info', message, onClose }) { const getClassName = () => { return `alert alert-${type}`; }; const getIcon = () => { switch(type) { case 'success': return '✓'; case 'error': return '✗'; case 'warning': return '!'; default: return 'i'; } }; return ( <div className={getClassName()}> <span className="icon">{getIcon()}</span> <p>{message}</p> {onClose && <button onClick={onClose}>×</button>} </div> ); } // الاستخدام function App() { return ( <div> <Alert type="success" message="تم تحديث الملف الشخصي بنجاح!" /> <Alert type="error" message="فشل في حفظ التغييرات" /> <Alert type="warning" message="ستنتهي جلستك قريباً" /> </div> ); }

مثال 3: مكون مؤشر التحميل

function LoadingSpinner({ size = 'medium', text = 'جارٍ التحميل...' }) { const sizeClasses = { small: 'spinner-small', medium: 'spinner-medium', large: 'spinner-large' }; return ( <div className="loading-container"> <div className={`spinner ${sizeClasses[size]}`}></div> {text && <p>{text}</p>} </div> ); } // الاستخدام function Dashboard() { const isLoading = true; return ( <div> {isLoading ? ( <LoadingSpinner size="large" text="جارٍ تحميل لوحة التحكم..." /> ) : ( <div>محتوى لوحة التحكم هنا</div> )} </div> ); }
التمرين 1: إنشاء مكون ProfileCard

بناء مكون ProfileCard يعرض معلومات الملف الشخصي للمستخدم:

المتطلبات:

  • قبول الخصائص: name, bio, avatar, skills (مصفوفة), isVerified (منطقي)
  • عرض صورة الرمز، الاسم، والسيرة الذاتية
  • إظهار شارة "موثق ✓" إذا كان isVerified صحيحاً
  • سرد جميع المهارات كحبوب/شارات
  • تضمين زر "تواصل"
  • استخدام قيم افتراضية للصورة والسيرة إذا لم يتم توفيرها

بيانات الاختبار:

const user = { name: 'نور الدين', bio: 'مطور Full-stack شغوف بـ React و Node.js', avatar: 'https://via.placeholder.com/150', skills: ['React', 'JavaScript', 'Node.js', 'MongoDB'], isVerified: true };
التمرين 2: بناء نظام تخطيط البطاقات

أنشئ نظام مكون بطاقة مرن مع التركيب:

المكونات التي يجب إنشاؤها:

  1. Card - حاوية رئيسية مع حدود وظل
  2. CardHeader - قسم الرأس مع العنوان
  3. CardBody - منطقة المحتوى
  4. CardFooter - التذييل مع الإجراءات

يجب أن يبدو الاستخدام كالتالي:

<Card> <CardHeader title="مرحباً" /> <CardBody> <p>هذا هو محتوى البطاقة.</p> </CardBody> <CardFooter> <button>معرفة المزيد</button> </CardFooter> </Card>
التمرين 3: إنشاء مكون StatusBadge

بناء مكون شارة حالة قابل لإعادة الاستخدام:

المتطلبات:

  • قبول الخصائص: status (سلسلة نصية), size (small/medium/large)
  • دعم الحالات: "active", "pending", "completed", "cancelled"
  • يجب أن يكون لكل حالة لون مختلف (أخضر، أصفر، أزرق، أحمر)
  • إضافة أيقونات أو رموز مناسبة لكل حالة
  • جعلها تعمل مع أحجام مختلفة

أمثلة الاستخدام:

<StatusBadge status="active" size="small" /> <StatusBadge status="pending" /> <StatusBadge status="completed" size="large" />

أنماط المكونات الشائعة

النمط 1: مكونات الحاوية

المكونات التي تدير الحالة والمنطق، وتمرير البيانات إلى مكونات العرض:

// مكون الحاوية (يدير البيانات) function UserListContainer() { const users = [ { id: 1, name: 'علي', role: 'مطور' }, { id: 2, name: 'فاطمة', role: 'مصممة' } ]; return <UserList users={users} />; } // مكون العرض (يعرض البيانات) function UserList({ users }) { return ( <ul> {users.map(user => ( <UserItem key={user.id} user={user} /> ))} </ul> ); }

النمط 2: الأغلفة الشرطية

function ConditionalWrapper({ condition, wrapper, children }) { return condition ? wrapper(children) : children; } // الاستخدام <ConditionalWrapper condition={isHighlighted} wrapper={children => <div className="highlight">{children}</div>} > <p>قد يتم تسليط الضوء على هذا المحتوى</p> </ConditionalWrapper>

الملخص

في هذا الدرس، تعلمت:

  • المكونات هي قطع قابلة لإعادة الاستخدام ومستقلة من واجهة المستخدم
  • المكونات الوظيفية هي دوال JavaScript تُرجع JSX
  • يجب أن تبدأ أسماء المكونات بحرف كبير (PascalCase)
  • تركيب المكونات يبني واجهات مستخدم معقدة من مكونات أصغر
  • Props تمرر البيانات من المكونات الأصلية إلى الفرعية
  • Props للقراءة فقط وتتدفق أحادية الاتجاه
  • تفكيك Props يجعل كود المكون أنظف
  • Props الافتراضية توفر قيم احتياطية
  • التنظيم المناسب للمكونات يحسن سهولة الصيانة
  • التصدير المسمى والافتراضي يتحكم في كيفية مشاركة المكونات

في الدرس التالي، سنتعمق أكثر في الخصائص، ونستكشف أنماط الخصائص المتقدمة، والتحقق من صحة الخصائص، وخاصية children، وكيفية التعامل مع تدفق البيانات المعقد بين المكونات!