React: تركيب المكونات وإعادة استخدامها
آخر تحديث: 2026-08-26
إن دمج المكونات يشبه البناء باستخدام مكعبات الليغو — فليس من الضروري أن تكون كل قطعة فريدة من نوعها. ويُقصد بتصميم المكونات الجيد أن تكون وحدات البناء الأساسية (المكونات الفرعية) مرنة ويمكن تجميعها بطرق متنوعة لإنشاء أي شكل.
1. ما ستتعلمه
- التكوين مقابل التوريث
- فتحات الأطفال ووضع الفتحات المتعددة
- مبادئ تحليل المكونات (المسؤولية الواحدة)
- نمط «Render Props»
- مقدمة إلى المكونات ذات الترتيب الأعلى (HOCs)
2. قصة نظام تصميم
(1) المشكلة: إعادة اختراع العجلة في كل صفحة
تشارلي هو مهندس الواجهة الأمامية في الشركة. وقد لاحظ أن كل صفحة كانت تعيد اختراع العجلة من جديد:
// Page A:Custom Pop-up Window
<div style={{
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
display: 'flex', alignItems: 'center', justifyContent: 'center'
}}>
<div style={{ backgroundColor: 'white', padding: '24px', borderRadius: '8px' }}>
<h2>Confirm Deletion</h2>
<p>Are you sure you want to delete this record??</p>
<button>Cancel</button>
<button>Confirm</button>
</div>
</div>
// Page B:Another pop-up,But the style is slightly different
<div style={{
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(0,0,0,0.3)',
display: 'flex', alignItems: 'center', justifyContent: 'center'
}}>
<div style={{ backgroundColor: 'white', padding: '32px', borderRadius: '12px' }}>
<h2>Edit User</h2>
<input placeholder="Username" />
<input placeholder="Email" />
<button>Save</button>
<button>Cancel</button>
</div>
</div>
المشكلة: تتكرر أنماط طبقة القناع والحاوية في صفحتين. إذا أردت تغيير حجم الزوايا الدائرية للنافذة المنبثقة، فسيتعين عليك تعديل 20 صفحة.
(2) حل النموذج التوافقي
// General Popup Component(Responsible only for structure and style,Not interested in the content)
function Modal({ isOpen, onClose, title, children }) {
if (!isOpen) return null
return (
<div style={overlayStyle} onClick={onClose}>
<div style={modalStyle} onClick={e => e.stopPropagation()}>
{title && <h2 style={{ margin: '0 0 16px 0' }}>{title}</h2>}
{children} {/* The content is determined by the user. */}
</div>
</div>
)
}
// Page A:Confirm Deletion
<Modal isOpen={showDelete} onClose={() => setShowDelete(false)} title="Confirm Deletion">
<p>Are you sure you want to delete this record??This action cannot be undone.。</p>
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
<button onClick={() => setShowDelete(false)}>Cancel</button>
<button style={{ backgroundColor: '#ff4d4f', color: 'white' }}>Confirm Deletion</button>
</div>
</Modal>
// Page B:Edit User
<Modal isOpen={showEdit} onClose={() => setShowEdit(false)} title="Edit User">
<input placeholder="Username" />
<input placeholder="Email" />
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '16px' }}>
<button>Save</button>
<button onClick={() => setShowEdit(false)}>Cancel</button>
</div>
</Modal>
المزايا: إدارة مركزية لأنماط النوافذ المنبثقة (تغيير في مكان واحد يؤثر على الكل)؛ المحتوى يحدده المستخدم بالكامل؛ إعادة استخدام مكون «Modal» واحد عبر 20 صفحة.
graph TB
subgraph "Combination Mode"
A[Modal Components] --> B[Overlay]
A --> C[Container modal]
C --> D[Title title]
C --> E[**children slot**]
E --> F[The content is determined by the parent component]
end
style E fill:#1890ff,color:#fff
3. خانة «الأطفال»
| وضع الفتحة | الصيغة | حالات الاستخدام |
|---|---|---|
| فتحة واحدة | {children} |
حاوية بسيطة (بطاقة، تخطيط) |
| متعدد الفتحات | {props.header} {props.footer} |
تخطيط متعدد المناطق (الصفحة، مربع الحوار) |
| renderProps | {props.renderItem(item)} |
العرض المخصص للقوائم/الجداول |
| المكون الفرعي للوظيفة | {props.children(data)} |
نشر البيانات + العرض المخصص |
(1) الاستخدام الأساسي
children هو عنصر Prop خاص يمثل المحتوى الموجود بين علامتي المكون.
// Component Definition
function Card({ title, children }) {
return (
<div style={{
border: '1px solid #f0f0f0',
borderRadius: '8px',
padding: '16px',
marginBottom: '16px'
}}>
{title && <h3 style={{ margin: '0 0 12px 0', color: '#333' }}>{title}</h3>}
<div>{children}</div>
</div>
)
}
// How to Use 1:A single element
<Card title="Announcement">
<p>The system will go live tonight 22:00 Perform maintenance</p>
</Card>
// How to Use 2:Multiple elements
<Card title="User Information">
<p>Name:Alice</p>
<p>Characters:Administrator</p>
<p>Email:alice@example.com</p>
</Card>
// How to Use 3:and even other components
<Card title="Statistical Overview">
<StatCard label="Number of users" value={1234} />
<StatCard label="Number of Orders" value={567} />
</Card>
(2) وضع الفتحات المتعددة
إذا كان أحد المكونات يتطلب مواضع فتحات متعددة، فيمكنك استخدام الخصائص المسماة (مثل header، footer، main):
function PageLayout({ header, sidebar, children, footer }) {
return (
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
{/* Header Socket */}
<header style={{ backgroundColor: '#001529', color: 'white', padding: '16px' }}>
{header}
</header>
<div style={{ display: 'flex', flex: 1 }}>
{/* Sidebar Slot */}
<aside style={{ width: '240px', backgroundColor: '#f5f5f5', padding: '16px' }}>
{sidebar}
</aside>
{/* Main Content Slot(children) */}
<main style={{ flex: 1, padding: '24px' }}>
{children}
</main>
</div>
{/* Bottom Slot */}
<footer style={{ backgroundColor: '#f0f0f0', padding: '12px', textAlign: 'center' }}>
{footer}
</footer>
</div>
)
}
// Usage
<PageLayout
header={<><h1>Management System</h1><nav>Navigation Menu</nav></>}
sidebar={<><h3>List of Features</h3><ul><li>User Management</li><li>Order Management</li></ul></>}
footer={<p>© 2026 web-tutorial.com</p>}
>
<h2>Welcome back,Administrator</h2>
<p>Today's Orders:128 orders</p>
<p>New Users:23 users</p>
</PageLayout>
▶ مثال: مكون قائمة عام
// ============================================
// Example:Generic List Component(Multi-slot + children)
// ============================================
function DataList({ data, renderItem, header, emptyMessage = 'No data available' }) {
return (
<div style={{ border: '1px solid #f0f0f0', borderRadius: '8px' }}>
{/* Header Socket */}
{header && <div style={{ padding: '12px 16px', borderBottom: '1px solid #f0f0f0', backgroundColor: '#fafafa' }}>{header}</div>}
{/* List Contents */}
{data.length === 0 ? (
<div style={{ padding: '40px', textAlign: 'center', color: '#999' }}>{emptyMessage}</div>
) : (
data.map((item, index) => (
<div key={item.id} style={{
padding: '12px 16px',
borderBottom: index < data.length - 1 ? '1px solid #f0f0f0' : 'none',
transition: 'background-color 0.2s'
}}
onMouseEnter={e => e.currentTarget.style.backgroundColor = '#fafafa'}
onMouseLeave={e => e.currentTarget.style.backgroundColor = 'transparent'}
>
{renderItem(item)}
</div>
))
)}
{/* children You can add additional action buttons */}
{data.length > 0 && <div style={{ padding: '12px 16px', borderTop: '1px solid #f0f0f0' }}>{data.length} records</div>}
</div>
)
}
// Examples of Use 1:User List
<DataList
data={users}
header={<h3 style={{ margin: 0 }}>User List</h3>}
renderItem={user => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{user.name}</span>
<span style={{ color: '#999' }}>{user.role}</span>
</div>
)}
/>
// Examples of Use 2:Product List
<DataList
data={products}
header={<h3 style={{ margin: 0 }}>Product List <button>Add Item</button></h3>}
renderItem={product => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{product.name}</span>
<span style={{ color: '#ff4d4f' }}>${product.price}</span>
</div>
)}
emptyMessage="No products have been listed yet."
/>
4. مبادئ تحليل المكونات
المكونات الجيدة تشبه الوظائف الجيدة — فهي ذات مسؤولية واحدة، وقابلة لإعادة الاستخدام، وسهلة الاختبار.
| المبدأ | الشرح | المثال المضاد |
|---|---|---|
| المسؤولية الواحدة | يقوم المكون بوظيفة واحدة فقط | يقوم المكون بعرض معلومات المستخدم ومعالجة عمليات تصدير البيانات في آن واحد |
| تقليل عدد الواجهات | كلما قل عدد الخصائص، كان ذلك أفضل (≤5) | يحتوي أحد المكونات على 15 خاصية |
| تدفق البيانات من الأعلى إلى الأسفل | تتدفق البيانات في اتجاه واحد من المكون الأصلي إلى المكون الفرعي | تقوم المكونات الفرعية بتعديل الحالة العامة مباشرةً |
| لا توجد آثار جانبية | تؤدي الخصائص المتطابقة إلى نتائج متطابقة | تعديل DOM أو استدعاء واجهات برمجة التطبيقات (APIs) مباشرةً داخل المكون |
▶ مثال: قبل التقسيم مقابل بعد التقسيم
// ============================================
// Example:Component Breakdown——from "Giant Components"to "Widget"
// ============================================
// ---- Before the split:One component does it all ----
function GiantUserPage({ userId }) {
const [user, setUser] = React.useState(null)
const [loading, setLoading] = React.useState(true)
const [editing, setEditing] = React.useState(false)
const [name, setName] = React.useState('')
const [email, setEmail] = React.useState('')
const [posts, setPosts] = React.useState([])
// ... 100 Multiple lines of logic mixed together
return (
<div>
{/* User Information、Edit Form、The list of articles is all crammed together */}
</div>
)
}
// ---- After the split:Each to their own role ----
function UserPage({ userId }) {
return (
<div>
<UserProfile userId={userId} /> {/* Responsible only for displaying user information */}
<UserEditForm userId={userId} /> {/* Responsible only for editing user content */}
<UserPosts userId={userId} /> {/* Responsible only for displaying the list of articles */}
</div>
)
}
// Each subcomponent can be developed independently.、Test、Reuse
function UserProfile({ userId }) { /* ... */ }
function UserEditForm({ userId }) { /* ... */ }
function UserPosts({ userId }) { /* ... */ }
(1) القواعد العامة للتقسيم
If a component exceeds 150 lines
A function that exceeds 30 lines
One render Return more than 10 JSX Element
Props More than 5
→ Consider a split!
5. نمط «Render Props»
يُعد «Render Props» نمطًا آخر من أنماط التركيب — فهو يتيح للمكون الأصلي التحكم في ما يعرضه المكون التابع من خلال خاصية دالة (function prop).
// ============================================
// Example:Render Props——Mouse Position Tracker
// ============================================
// Child component:Responsible only for"Track Mouse Position",Don't care"How to display"
function MouseTracker({ render }) {
const [position, setPosition] = React.useState({ x: 0, y: 0 })
function handleMouseMove(e) {
setPosition({ x: e.clientX, y: e.clientY })
}
return (
<div style={{ height: '300px', border: '1px solid #ddd' }} onMouseMove={handleMouseMove}>
{/* Let the parent component determine how to render the mouse position */}
{render(position)}
</div>
)
}
// Parent Component:Decision"How to display"
function App() {
return (
<div>
<h2>Render Props Example</h2>
{/* Usage 1:Display Coordinates */}
<MouseTracker render={({ x, y }) => (
<div style={{ padding: '20px' }}>
<p>Mouse Position:({x}, {y})</p>
</div>
)} />
{/* Usage 2:Display Coordinate Indicator */}
<MouseTracker render={({ x, y }) => (
<div style={{
position: 'absolute', left: x, top: y,
width: '20px', height: '20px',
backgroundColor: '#1890ff', borderRadius: '50%',
transform: 'translate(-50%, -50%)',
pointerEvents: 'none'
}} />
)} />
</div>
)
}
ملاحظة: منذ طرح React Hooks، أصبح من الممكن استبدال معظم حالات استخدام Render Props بـ hooks مخصصة (التي تتميز بأنها أكثر إيجازًا). وعادةً ما توجد Render Props في الكود القديم.
6. التركيب مقابل التوريث
يؤكد فريق React صراحةً: لا تستخدم الوراثة. استخدم التركيب.
// ❌ Inheritance Patterns(Don't write it that way.)
class BaseButton extends React.Component {
// ...Overriding Methods After Inheritance in a Subclass,React Not recommended
}
class PrimaryButton extends BaseButton { }
// ✅ Combination Mode(The Correct Way to Do It)
function Button({ variant = 'default', children, ...props }) {
const styles = {
primary: { backgroundColor: '#1890ff', color: 'white' },
danger: { backgroundColor: '#ff4d4f', color: 'white' },
default: { backgroundColor: '#f0f0f0', color: '#333' }
}
return <button style={styles[variant]} {...props}>{children}</button>
}
// Through Props Control Variants,rather than through inheritance
<Button variant="primary">Main Buttons</Button>
<Button variant="danger">Danger Button</Button>
<Button variant="default">Default Button</Button>
| البعد | التجميع (موصى به) | التوريث (غير موصى به) |
|---|---|---|
| المرونة | عالية — يمكن دمجها بحرية باستخدام العناصر المساعدة والعناصر التابعة | منخفضة — سلسلة وراثة ثابتة |
| قابلية الاختبار | عالية — يمكن اختبار كل مكون على حدة | منخفضة — تعتمد على تنفيذ الفئة الأم |
| أمان الأنواع | جيد — يتم تحديد أنواع المتغيرات المساعدة بشكل صريح | ضعيف — تجاوز الطرق عرضة للأخطاء |
7. مثال كامل: نظام بطاقات لوحة التحكم القابلة لإعادة الاستخدام
// ============================================
// Complete Example:Dashboard Card System
// Features:Combination Mode + children + Props Integrated Use of
// ============================================
// ---- 1. Basic Components:Card Holder ----
function DashboardCard({ title, icon, action, children, width = '300px' }) {
return (
<div style={{
border: '1px solid #f0f0f0',
borderRadius: '8px',
width,
backgroundColor: 'white',
boxShadow: '0 2px 8px rgba(0,0,0,0.08)'
}}>
{/* Card Header */}
<div style={{
display: 'flex', justifyContent: 'space-between',
alignItems: 'center', padding: '16px',
borderBottom: '1px solid #f0f0f0'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
{icon && <span>{icon}</span>}
<h3 style={{ margin: 0, fontSize: '16px' }}>{title}</h3>
</div>
{action && <div>{action}</div>}
</div>
{/* Card Content Slot */}
<div style={{ padding: '16px' }}>
{children}
</div>
</div>
)
}
// ---- 2. Statistics Card ----
function StatCard({ label, value, trend, color = '#1890ff' }) {
return (
<div style={{ textAlign: 'center', padding: '8px 0' }}>
<p style={{ color: '#666', fontSize: '14px', margin: '0 0 8px 0' }}>{label}</p>
<p style={{ fontSize: '32px', fontWeight: 'bold', color, margin: '0 0 8px 0' }}>{value}</p>
{trend !== undefined && (
<p style={{
color: trend >= 0 ? '#52c41a' : '#ff4d4f',
fontSize: '14px', margin: 0
}}>
{trend >= 0 ? '↑' : '↓'} {Math.abs(trend)}%
</p>
)}
</div>
)
}
// ---- 3. List Cards ----
function ListCard({ title, icon, items, renderItem, action, emptyText = 'No data available' }) {
return (
<DashboardCard title={title} icon={icon} action={action}>
{items.length === 0 ? (
<p style={{ textAlign: 'center', color: '#999', padding: '20px' }}>{emptyText}</p>
) : (
items.map((item, index) => (
<div key={item.id} style={{
padding: '8px 0',
borderBottom: index < items.length - 1 ? '1px solid #f5f5f5' : 'none'
}}>
{renderItem(item)}
</div>
))
)}
</DashboardCard>
)
}
// ---- 4. Usage:Complete Dashboard ----
function Dashboard() {
const stats = [
{ id: 'users', label: 'Total Users', value: '12,846', trend: 12 },
{ id: 'orders', label: 'Today's Orders', value: '328', trend: -3 },
{ id: 'revenue', label: 'Monthly Income', value: '$89,200', trend: 25 },
{ id: 'visits', label: 'Visits Today', value: '4,621', trend: 8 }
]
const recentOrders = [
{ id: 1, customer: 'Alice', product: 'React Tutorial', amount: 89, status: 'Completed' },
{ id: 2, customer: 'Bob', product: 'TypeScript Getting Started', amount: 59, status: 'Processing...' },
{ id: 3, customer: 'Charlie', product: 'Node.js Real-World Experience', amount: 129, status: 'Completed' }
]
return (
<div>
<h2>📊 Management Dashboard</h2>
{/* Statistical Card Rows(Grid Layout) */}
<div style={{ display: 'flex', gap: '16px', marginBottom: '24px', flexWrap: 'wrap' }}>
<DashboardCard title="Data Overview" icon="📈" width="100%">
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }}>
{stats.map(s => (
<StatCard key={s.id} {...s} />
))}
</div>
</DashboardCard>
</div>
{/* List of Recent Orders */}
<div style={{ display: 'flex', gap: '16px' }}>
<ListCard
title="Recent Orders"
icon="📋"
items={recentOrders}
action={<button style={btnStyle}>View All</button>}
renderItem={order => (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<p style={{ margin: 0, fontWeight: 'bold' }}>{order.customer}</p>
<p style={{ margin: '4px 0 0 0', color: '#666', fontSize: '13px' }}>{order.product}</p>
</div>
<div style={{ textAlign: 'right' }}>
<p style={{ margin: 0, color: '#ff4d4f' }}>${order.amount}</p>
<span style={{
fontSize: '12px', padding: '2px 6px', borderRadius: '4px',
backgroundColor: order.status === 'Completed' ? '#f6ffed' : '#fff7e6',
color: order.status === 'Completed' ? '#52c41a' : '#faad14'
}}>
{order.status}
</span>
</div>
</div>
)}
/>
{/* Custom Cards:Use directly children */}
<DashboardCard title="Quick Actions" icon="⚡" width="300px">
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<button style={actionBtnStyle}>📝 Create a New Order</button>
<button style={actionBtnStyle}>👤 Add User</button>
<button style={actionBtnStyle}>📊 Generate Reports</button>
<button style={actionBtnStyle}>⚙️ System Settings</button>
</div>
</DashboardCard>
</div>
</div>
)
}
const btnStyle = {
padding: '4px 12px', border: '1px solid #d9d9d9',
borderRadius: '4px', backgroundColor: 'white',
cursor: 'pointer', fontSize: '12px'
}
const actionBtnStyle = {
width: '100%', padding: '10px', border: 'none',
borderRadius: '4px', backgroundColor: '#f5f5f5',
cursor: 'pointer', textAlign: 'left', fontSize: '14px',
transition: 'background-color 0.2s'
}
// Hover effects are enabled via onMouseEnter/Leave Add,Omitted here
الناتج المتوقع: صفحة لوحة تحكم كاملة تتضمن بطاقة نظرة عامة على البيانات (تحتوي على 4 مؤشرات) في الجزء العلوي، وقائمة بالطلبات الحديثة في الجزء السفلي الأيسر، ومجموعة من أزرار الإجراءات السريعة في الجزء السفلي الأيمن. تم إنشاء جميع البطاقات باستخدام نمط التكوين.
▶ المثال 3: خصائص العرض — الجداول القابلة للفرز
function SortableTable({ data, columns, renderCell }) {
const [sortKey, setSortKey] = useState(null)
const [sortDir, setSortDir] = useState('asc')
const sorted = useMemo(() => {
if (!sortKey) return data
return [...data].sort((a, b) => {
const diff = a[sortKey] > b[sortKey] ? 1 : a[sortKey] < b[sortKey] ? -1 : 0
return sortDir === 'asc' ? diff : -diff
})
}, [data, sortKey, sortDir])
function handleSort(key) {
if (sortKey === key) setSortDir(d => d === 'asc' ? 'desc' : 'asc')
else { setSortKey(key); setSortDir('asc') }
}
return (
<table border={1} cellPadding={8} style={{ borderCollapse: 'collapse', width: '100%' }}>
<thead>
<tr>
{columns.map(col => (
<th key={col.key} onClick={() => handleSort(col.key)} style={{ cursor: 'pointer' }}>
{col.label} {sortKey === col.key ? (sortDir === 'asc' ? '↑' : '↓') : ''}
</th>
))}
</tr>
</thead>
<tbody>
{sorted.map((row, i) => (
<tr key={i}>{columns.map(col => <td key={col.key}>{renderCell(row, col.key)}</td>)}</tr>
))}
</tbody>
</table>
)
}
function App() {
const users = [
{ name: 'Alice', score: 92, role: 'Admin' },
{ name: 'Bob', score: 85, role: 'Editor' },
{ name: 'Charlie', score: 78, role: 'Viewer' },
]
const cols = [{ key: 'name', label: 'Name' }, { key: 'score', label: 'Score' }, { key: 'role', label: 'Role' }]
return (
<div style={{ maxWidth: 500, margin: '0 auto' }}>
<h3>Sortable Users</h3>
<SortableTable data={users} columns={cols}
renderCell={(row, key) => key === 'score' ? `${row[key]} pts` : row[key]} />
</div>
)
}
▶ المثال 4: تركيبات التخطيط — تخطيطات متعددة المناطق
function AppLayout({ sidebar, header, content, footer }) {
return (
<div style={{ display: 'flex', minHeight: '100vh' }}>
{sidebar && <aside style={{ width: 200, background: '#f5f5f5', padding: 16 }}>{sidebar}</aside>}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
{header && <header style={{ padding: '12px 16px', background: '#1890ff', color: 'white' }}>{header}</header>}
<main style={{ flex: 1, padding: 16 }}>{content}</main>
{footer && <footer style={{ padding: '8px 16px', background: '#fafafa', fontSize: 12, color: '#999' }}>{footer}</footer>}
</div>
</div>
)
}
function Dashboard() {
return (
<AppLayout
sidebar={<nav><p>Menu</p><ul><li>Home</li><li>Settings</li></ul></nav>}
header={<h2 style={{ margin: 0 }}>Dashboard</h2>}
content={<p>Welcome back, Alice! You have 5 new tasks.</p>}
footer={<span>© 2026 SaaS App</span>}
/>
)
}
▶ المثال 5: التركيب مقابل الوراثة — مكون النافذة المنبثقة
function Modal({ isOpen, onClose, title, children, footer }) {
if (!isOpen) return null
return (
<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}
onClick={onClose}>
<div style={{ background: 'white', borderRadius: 8, padding: 24, minWidth: 320, maxWidth: 500 }}
onClick={e => e.stopPropagation()}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<h3 style={{ margin: 0 }}>{title}</h3>
<button onClick={onClose} style={{ border: 'none', background: 'none', cursor: 'pointer', fontSize: 18 }}>✕</button>
</div>
<div style={{ marginBottom: 16 }}>{children}</div>
{footer && <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>{footer}</div>}
</div>
</div>
)
}
function ConfirmModal({ isOpen, onConfirm, onCancel, message }) {
return (
<Modal isOpen={isOpen} onClose={onCancel} title="Confirm"
footer={<>
<button onClick={onCancel} style={{ padding: '6px 16px', cursor: 'pointer' }}>Cancel</button>
<button onClick={onConfirm} style={{ padding: '6px 16px', background: '#ff4d4f', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>Confirm</button>
</>}>
<p>{message}</p>
</Modal>
)
}
function FormModal({ isOpen, onClose }) {
const [value, setValue] = useState('')
return (
<Modal isOpen={isOpen} onClose={onClose} title="New Item"
footer={<button onClick={() => { alert(value); onClose() }} style={{ padding: '6px 16px', background: '#1890ff', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>Submit</button>}>
<input value={value} onChange={e => setValue(e.target.value)} placeholder="Enter name" style={{ width: '100%', padding: 8, borderRadius: 4 }} />
</Modal>
)
}
❓ أسئلة شائعة
children، ومتى يجب استخدام Props؟children لمكونات الحاويات التي تحتوي على «مناطق محتوى متغيرة» (مثل النوافذ المنبثقة، والبطاقات، والتخطيطات). يُستخدم Props للمكونات ذات «المحتوى القابل للتكوين» (مثل ألوان الأزرار ونص العناوين). ببساطة: استخدم children للعناصر الهيكلية وProps للتكوين.UserForm يحتوي على 12 خاصية، فقم بتقسيمه إلى ثلاثة مكونات فرعية: UserBasicInfo (الاسم، البريد الإلكتروني، رقم الهاتف)، UserAddress (المحافظة، المدينة، المنطقة، العنوان التفصيلي)، وUserSettings (الدور، الأذونات).useXxx() سطر واحد فقط من الكود، مما ينتج عنه كود أكثر بساطة وسهولة في القراءة. لا تتمتع Render Props بميزة إلا عندما يحتاج المنطق المعاد استخدامه إلى «عرض جزء من واجهة المستخدم» (مثل عندما يحتاج مكون تلميح الأداة إلى معرفة موضع عناصره الفرعية).📖 ملخص
- التركيب أفضل من الوراثة — في React، نستخدم التركيب في جميع الأحوال ولا نستخدم الوراثة أبدًا.
childrenهو عنصر خاص يطبق نمط «الفتحة» (slot)، مما يتيح تداخل المكونات بمرونة- يمكن استخدام وضع «الفتحات المتعددة» مع العناصر المسماة (header/footer/sidebar) لتنفيذ تخطيطات معقدة
- يتبع تفكيك المكونات مبدأ المسؤولية الواحدة: لا يقوم المكون إلا بوظيفة واحدة
- تُستخدم «Render Props» لتمرير وظائف العرض؛ وتُعد «Hooks» بديلاً أفضل.
📝 تمارين
- المشكلة الأساسية (صعوبة ⭐): أنشئ مكونًا
Badgeيقبلcount(رقمًا) وchildren(محتوى)، ويعرض رقمًا أحمر مكتوبًا في الأعلى في الزاوية اليمنى العليا. - تمرين متقدم (درجة الصعوبة ⭐⭐): أنشئ مكونًا
Tabsواستخدم نمط التجميع (Composite Pattern) لتنفيذ تبديل التسميات:<Tabs><TabPanel label="Tab1">inside content 1</TabPanel><TabPanel label="Tab2">inside content 2</TabPanel></Tabs>. - التحدي (الصعوبة: ⭐⭐⭐): قم بإنشاء مكون
Tableباستخدام نمط Render Props:<Table data={users} columns={[{ title: 'Name', render: u => u.name }, { title: 'Age', render: u => u.age }]} />، مع دعم ميزة الفرز وتمييز الألوان بنمط «الزيبرا».