فهم الخصائص بعمق
Props (الخصائص) هي آلية React لتمرير البيانات والوظائف من المكونات الأصلية إلى المكونات الفرعية. إنها الطريقة الأساسية التي تتواصل بها المكونات مع بعضها البعض في معمارية تدفق البيانات أحادي الاتجاه في React.
المفاهيم الأساسية للخصائص:
- الخصائص للقراءة فقط - لا يمكن للمكونات تعديل خصائصها الخاصة
- تتدفق الخصائص للأسفل - من الأصل إلى الفرع، وليس للأعلى أبداً
- يمكن أن تكون الخصائص أي نوع بيانات - سلاسل نصية، أرقام، كائنات، مصفوفات، دوال، حتى مكونات أخرى
- تجعل الخصائص المكونات قابلة لإعادة الاستخدام - يمكن للمكون نفسه عرض بيانات مختلفة
تمرير أنواع مختلفة من البيانات كخصائص
1. خصائص السلاسل النصية
// يمكن تمرير السلاسل النصية مع أو بدون أقواس معقوفة
<Button text="إرسال" />
<Button text={'إرسال'} /> // صالح أيضاً، لكن علامات الاقتباس مفضلة
// سلاسل نصية متعددة الأسطر
<Description
text="هذا وصف طويل جداً يمتد على
عدة أسطر ويحتوي على الكثير من التفاصيل."
/>
2. خصائص الأرقام
// يجب أن تستخدم الأرقام أقواس معقوفة
<Counter count={42} />
<Price value={19.99} />
<RatingStars rating={4.5} />
function Counter({ count }) {
return <div>العدد: {count}</div>;
}
3. خصائص منطقية
// قيم منطقية صريحة
<Button disabled={true} />
<Modal isOpen={false} />
// اختصار لـ true (فقط اسم السمة)
<Button disabled />
<Input required />
<Checkbox checked />
// هذه متكافئة:
<Button disabled={true} />
<Button disabled />
4. خصائص المصفوفات
function TagList({ tags }) {
return (
<div className="tags">
{tags.map((tag, index) => (
<span key={index} className="tag">{tag}</span>
))}
</div>
);
}
// الاستخدام
<TagList tags={['React', 'JavaScript', 'الواجهة الأمامية']} />
5. خصائص الكائنات
function UserProfile({ user }) {
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<p>العمر: {user.age}</p>
</div>
);
}
// الاستخدام
<UserProfile
user={{
name: 'سارة أحمد',
email: 'sarah@example.com',
age: 28
}}
/>
ملاحظة الأداء: عند تمرير كائنات حرفية مباشرة في JSX (مثل user={{name: 'سارة'}})، يتم إنشاء كائن جديد في كل عرض. لأداء أفضل، عرّف الكائنات خارج المكون أو في الحالة.
6. خصائص الدوال (Callbacks)
function Button({ onClick, text }) {
return <button onClick={onClick}>{text}</button>;
}
function App() {
const handleClick = () => {
console.log('تم النقر على الزر!');
};
return <Button onClick={handleClick} text="انقر هنا" />;
}
خاصية Children
خاصية children هي خاصية خاصة تمثل المحتوى بين وسوم الفتح والإغلاق للمكون. إنها واحدة من أقوى ميزات React لإنشاء مكونات مرنة وقابلة للتركيب.
// الاستخدام الأساسي لـ children
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
// الاستخدام
<Card>
<h2>عنوان البطاقة</h2>
<p>محتوى البطاقة هنا.</p>
</Card>
يمكن أن يكون Children أي شيء:
// children سلسلة نصية
<Button>انقر هنا</Button>
// children JSX
<Container>
<Header />
<Content />
<Footer />
</Container>
// children محتوى مختلط
<Alert>
<strong>تحذير:</strong> لا يمكن التراجع عن هذا الإجراء!
</Alert>
// children دالة (نمط render props)
<DataProvider>
{data => <div>{data.message}</div>}
</DataProvider>
مثال عملي: مكونات التخطيط
// مكون تخطيط مرن
function PageLayout({ children }) {
return (
<div className="page-layout">
<header className="header">
<h1>تطبيقي</h1>
</header>
<main className="main-content">
{children}
</main>
<footer className="footer">
<p>© 2024 اسم الشركة</p>
</footer>
</div>
);
}
// الاستخدام - محتوى مختلف لصفحات مختلفة
function HomePage() {
return (
<PageLayout>
<h2>مرحباً بكم في الصفحة الرئيسية!</h2>
<p>هذا محتوى الصفحة الرئيسية.</p>
</PageLayout>
);
}
function AboutPage() {
return (
<PageLayout>
<h2>من نحن</h2>
<p>هذا محتوى صفحة من نحن.</p>
</PageLayout>
);
}
أنماط تفكيك الخصائص
1. التفكيك الأساسي
// بدون تفكيك
function UserCard(props) {
return (
<div>
<h2>{props.name}</h2>
<p>{props.email}</p>
</div>
);
}
// مع التفكيك (مُفضل)
function UserCard({ name, email }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
2. التفكيك مع القيم الافتراضية
function Button({ text = 'انقر هنا', type = 'button', disabled = false }) {
return (
<button type={type} disabled={disabled}>
{text}
</button>
);
}
// كل هذه صالحة:
<Button /> // يستخدم جميع الافتراضات
<Button text="إرسال" /> // يستبدل النص فقط
<Button text="حذف" type="button" disabled /> // يستبدل الكل
3. معاملات Rest (نشر الخصائص المتبقية)
function Button({ text, variant, ...otherProps }) {
// استخراج خصائص محددة، نشر الباقي
return (
<button className={`btn btn-${variant}`} {...otherProps}>
{text}
</button>
);
}
// الاستخدام - onClick, disabled, إلخ يتم نشرها على الزر
<Button
text="إرسال"
variant="primary"
onClick={handleClick}
disabled={isLoading}
data-testid="submit-btn"
/>
4. تفكيك الكائنات المتداخلة
function UserProfile({ user: { name, email, address } }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
<p>{address}</p>
</div>
);
}
// الاستخدام
<UserProfile
user={{
name: 'علي',
email: 'ali@example.com',
address: '123 الشارع الرئيسي'
}}
/>
Prop Drilling وتحدياته
يحدث Prop drilling عندما تمرر الخصائص عبر طبقات متعددة من المكونات، حتى عندما لا تحتاج المكونات الوسيطة إلى تلك الخصائص.
// مثال على Prop drilling
function App() {
const user = { name: 'علي', role: 'مشرف' };
return <Dashboard user={user} />;
}
function Dashboard({ user }) {
// Dashboard لا يستخدم user، فقط يمرره للأسفل
return (
<div>
<Sidebar user={user} />
</div>
);
}
function Sidebar({ user }) {
// Sidebar لا يستخدم user أيضاً، فقط يمرره للأسفل
return (
<div>
<UserMenu user={user} />
</div>
);
}
function UserMenu({ user }) {
// أخيراً! UserMenu يستخدم خاصية user فعلياً
return (
<div>
<p>مرحباً، {user.name}!</p>
<p>الدور: {user.role}</p>
</div>
);
}
مشاكل Prop Drilling:
- يجعل المكونات أقل قابلية لإعادة الاستخدام (تتطلب خصائص لا تستخدمها)
- أصعب في إعادة الهيكلة (تغيير الخصائص يؤثر على العديد من المكونات)
- كود أكثر إسهاباً مع تمرير الخصائص المتكرر
- صعوبة تتبع تدفق البيانات في التطبيقات الكبيرة
الحلول تشمل Context API (سيتم تغطيته في دروس لاحقة)، مكتبات إدارة الحالة، أو أنماط تركيب المكونات.
خصائص Callback: التواصل بين الأصل والفرع
بينما تتدفق الخصائص للأسفل، تسمح callbacks للفروع بالتواصل مع الأصول:
function Parent() {
const handleChildClick = (childName) => {
console.log(`تم النقر على ${childName}!`);
};
return (
<div>
<Child name="الفرع 1" onChildClick={handleChildClick} />
<Child name="الفرع 2" onChildClick={handleChildClick} />
</div>
);
}
function Child({ name, onChildClick }) {
return (
<button onClick={() => onChildClick(name)}>
انقر على {name}
</button>
);
}
مثال عملي: نموذج مع Callbacks
function LoginForm() {
const handleSubmit = (email, password) => {
console.log('تسجيل الدخول باستخدام:', email, password);
// إجراء استدعاء API هنا
};
return (
<div>
<h2>تسجيل الدخول</h2>
<Form onSubmit={handleSubmit} />
</div>
);
}
function Form({ onSubmit }) {
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const handleFormSubmit = (e) => {
e.preventDefault();
onSubmit(email, password);
};
return (
<form onSubmit={handleFormSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="البريد الإلكتروني"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="كلمة المرور"
/>
<button type="submit">تسجيل الدخول</button>
</form>
);
}
أنواع الخصائص والتحقق
بينما لا يفرض React أنواع الخصائص افتراضياً، يمكنك إضافة التحقق باستخدام PropTypes أو TypeScript:
// استخدام PropTypes (يتطلب حزمة prop-types)
import PropTypes from 'prop-types';
function UserCard({ name, age, email, isActive }) {
return (
<div>
<h2>{name}</h2>
<p>العمر: {age}</p>
<p>البريد الإلكتروني: {email}</p>
{isActive && <span>نشط</span>}
</div>
);
}
// تحديد أنواع الخصائص
UserCard.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string.isRequired,
isActive: PropTypes.bool
};
// تحديد الخصائص الافتراضية
UserCard.defaultProps = {
isActive: false
};
PropTypes مقابل TypeScript: PropTypes توفر التحقق في وقت التشغيل (تظهر الأخطاء في وحدة التحكم). TypeScript يوفر فحص الأنواع في وقت الترجمة (أخطاء أثناء التطوير). تطبيقات React الحديثة تستخدم بشكل متزايد TypeScript للحصول على أمان نوعي أفضل.
أنماط الخصائص المتقدمة
1. نمط Render Props
// مكون يشارك المنطق من خلال render prop
function Mouse({ render }) {
const [position, setPosition] = React.useState({ x: 0, y: 0 });
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
return (
<div onMouseMove={handleMouseMove}>
{render(position)}
</div>
);
}
// الاستخدام
<Mouse render={({ x, y }) => (
<div>
<h1>موضع الفأرة</h1>
<p>X: {x}, Y: {y}</p>
</div>
)} />
2. المكون كخصائص
function Layout({ Header, Content, Footer }) {
return (
<div className="layout">
<Header />
<Content />
<Footer />
</div>
);
}
// الاستخدام
<Layout
Header={() => <header>تطبيقي</header>}
Content={() => <main>المحتوى هنا</main>}
Footer={() => <footer>© 2024</footer>}
/>
3. نمط نشر الخصائص
// مكون غلاف يمرر جميع الخصائص
function StyledButton(props) {
return <button {...props} className="styled-btn" />;
}
// جميع الخصائص (onClick, disabled, إلخ) يتم تمريرها إلى الزر
<StyledButton onClick={handleClick} disabled={isLoading}>
إرسال
</StyledButton>
أفضل الممارسات للخصائص
1. اجعل أسماء الخصائص واضحة ومتسقة
✅ جيد: <Button onClick={handleClick} isLoading={loading} />
❌ سيئ: <Button click={handleClick} load={loading} />
2. استخدم الخصائص المنطقية مع بادئة "is/has"
✅ جيد: isActive, isLoading, hasError, canEdit
❌ سيئ: active, loading, error, edit
3. اجعل المكونات مركزة
✅ جيد: <Button text="إرسال" onClick={handleSubmit} />
❌ سيئ: <Button text="إرسال" onClick={handleSubmit}
color="blue" fontSize={16} padding={10}
border="1px solid" ... />
// بدلاً من ذلك، استخدم className أو خصائص style
4. تجنب تمرير الكثير من الخصائص
إذا احتاج المكون إلى 10+ خصائص، فكر في:
- تقسيمه إلى مكونات أصغر
- تجميع الخصائص ذات الصلة في كائنات
- استخدام التركيب بدلاً من ذلك
5. وثق الخصائص المعقدة
// أضف تعليقات لهياكل الخصائص المعقدة
/**
* @param {Object} user - كائن المستخدم
* @param {string} user.name - الاسم الكامل للمستخدم
* @param {string} user.email - البريد الإلكتروني للمستخدم
* @param {number} user.age - عمر المستخدم
*/
function UserProfile({ user }) { ... }
التمرين 1: قائمة المنتجات مع Callbacks
أنشئ نظام قائمة منتجات حيث تتواصل المكونات الفرعية مع الأصل:
المتطلبات:
- أنشئ مكون
ProductList أصلي
- أنشئ مكون
ProductCard فرعي
- يجب أن يحتوي ProductCard على زر "أضف إلى السلة"
- عند النقر، يجب أن يستدعي callback prop بمعلومات المنتج
- يجب على الأصل تسجيل أي منتج تمت إضافته
- مرر مصفوفة من المنتجات كخصائص
بيانات الاختبار:
const products = [
{ id: 1, name: 'لابتوب', price: 999 },
{ id: 2, name: 'فأرة', price: 29 },
{ id: 3, name: 'لوحة مفاتيح', price: 79 }
];
التمرين 2: مكون بطاقة مرن مع Children
بناء نظام مكون بطاقة مرن باستخدام خاصية children:
المتطلبات:
- أنشئ مكون
Card يقبل children
- أضف خصائص اختيارية: variant ('default', 'primary', 'danger'), padding, shadow
- صمم البطاقة بشكل مختلف بناءً على variant
- أنشئ 3 بطاقات مختلفة بمحتوى مختلف باستخدام children
- يجب أن تحتوي بطاقة واحدة على نص، وواحدة على صورة ونص، وواحدة على نموذج
التمرين 3: تحدي تدفق البيانات
بناء قائمة مهام حيث تتدفق البيانات بين المكونات:
هيكل المكونات:
<TodoApp>
<TodoInput onAddTodo={callback} />
<TodoList todos={array}>
<TodoItem todo={object} onToggle={callback} onDelete={callback} />
</TodoList>
</TodoApp>
المتطلبات:
- TodoApp يدير مصفوفة قائمة المهام
- TodoInput يتلقى callback لإضافة مهام جديدة
- TodoList يتلقى مصفوفة todos ويعرض TodoItems
- كل TodoItem يتلقى كائن todo وcallbacks للتبديل/الحذف
- أظهر تمرير الخصائص الصحيح عبر جميع المستويات
أخطاء الخصائص الشائعة التي يجب تجنبها
1. تعديل الخصائص
// ❌ لا تعدل الخصائص مباشرة أبداً
function BadComponent({ items }) {
items.push('عنصر جديد'); // خطأ!
return <div>{items.length}</div>;
}
// ✅ أنشئ مصفوفات جديدة بدلاً من ذلك
function GoodComponent({ items, onAddItem }) {
const newItems = [...items, 'عنصر جديد'];
onAddItem(newItems);
return <div>{newItems.length}</div>;
}
2. إنشاء دوال في العرض
// ❌ ينشئ دالة جديدة في كل عرض
<Button onClick={() => handleClick(id)} />
// ✅ أنشئ الدالة مرة واحدة أو استخدم useCallback (درس لاحق)
const handleButtonClick = () => handleClick(id);
<Button onClick={handleButtonClick} />
3. تمرير الحالة بالكامل كخصائص
// ❌ الفرع يحصل على الحالة بالكامل، حتى البيانات التي لا يحتاجها
<UserProfile state={this.state} />
// ✅ مرر فقط ما هو مطلوب
<UserProfile name={state.name} email={state.email} />
الملخص
في هذا الدرس، أتقنت الخصائص وتدفق البيانات في React:
- الخصائص هي بيانات للقراءة فقط يتم تمريرها من المكونات الأصلية إلى الفرعية
- يمكن أن تكون الخصائص أي نوع بيانات: سلاسل نصية، أرقام، منطقية، كائنات، مصفوفات، دوال
- خاصية children توفر تركيب مكون مرن
- تفكيك الخصائص يجعل كود المكون أنظف وأكثر قابلية للقراءة
- خصائص Callback تمكّن التواصل من الفرع إلى الأصل
- Prop drilling يمكن أن يصبح مشكلة في المكونات المتداخلة بعمق
- PropTypes و TypeScript توفر التحقق من الخصائص وأمان النوع
- أنماط متقدمة متنوعة (render props، component props، spread props)
- أفضل الممارسات تشمل التسمية الواضحة والمكونات المركزة والتوثيق المناسب
في الدرس التالي، سنستكشف React State - كيف تدير المكونات وتحدث بياناتها الخاصة، مما يجعل تطبيقاتك تفاعلية وديناميكية حقاً!