React: تركيب المكونات وإعادة استخدامها

آخر تحديث: 2026-08-26

إن دمج المكونات يشبه البناء باستخدام مكعبات الليغو — فليس من الضروري أن تكون كل قطعة فريدة من نوعها. ويُقصد بتصميم المكونات الجيد أن تكون وحدات البناء الأساسية (المكونات الفرعية) مرنة ويمكن تجميعها بطرق متنوعة لإنشاء أي شكل.


1. ما ستتعلمه



2. قصة نظام تصميم

(1) المشكلة: إعادة اختراع العجلة في كل صفحة

تشارلي هو مهندس الواجهة الأمامية في الشركة. وقد لاحظ أن كل صفحة كانت تعيد اختراع العجلة من جديد:

JSX
// 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) حل النموذج التوافقي

JSX
// 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 صفحة.

100%
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 خاص يمثل المحتوى الموجود بين علامتي المكون.

JSX
// 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):

JSX
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>
▶ جرّب الكود

▶ مثال: مكون قائمة عام

JSX 📖 للعرض فقط
// ============================================
// 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."
/>
48 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

4. مبادئ تحليل المكونات

المكونات الجيدة تشبه الوظائف الجيدة — فهي ذات مسؤولية واحدة، وقابلة لإعادة الاستخدام، وسهلة الاختبار.

المبدأ الشرح المثال المضاد
المسؤولية الواحدة يقوم المكون بوظيفة واحدة فقط يقوم المكون بعرض معلومات المستخدم ومعالجة عمليات تصدير البيانات في آن واحد
تقليل عدد الواجهات كلما قل عدد الخصائص، كان ذلك أفضل (≤5) يحتوي أحد المكونات على 15 خاصية
تدفق البيانات من الأعلى إلى الأسفل تتدفق البيانات في اتجاه واحد من المكون الأصلي إلى المكون الفرعي تقوم المكونات الفرعية بتعديل الحالة العامة مباشرةً
لا توجد آثار جانبية تؤدي الخصائص المتطابقة إلى نتائج متطابقة تعديل DOM أو استدعاء واجهات برمجة التطبيقات (APIs) مباشرةً داخل المكون

▶ مثال: قبل التقسيم مقابل بعد التقسيم

JSX
// ============================================
// 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) القواعد العامة للتقسيم

BASH
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).

JSX
// ============================================
// 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 صراحةً: لا تستخدم الوراثة. استخدم التركيب.

JSX
// ❌ 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. مثال كامل: نظام بطاقات لوحة التحكم القابلة لإعادة الاستخدام

JSX
// ============================================
// 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: خصائص العرض — الجداول القابلة للفرز

JSX
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: تركيبات التخطيط — تخطيطات متعددة المناطق

JSX
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: التركيب مقابل الوراثة — مكون النافذة المنبثقة

JSX
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 للتكوين.
س كيف أختار بين Render Props و Hooks؟
ج بالنسبة للكود الجديد، أعطِ الأولوية لـ Hooks المخصصة. تُعد Render Props مناسبة للحالات التي تتضمن كلاً من منطق الحالة وهيكل واجهة المستخدم (مثل MouseTracker، التي تتطلب عنصر DOM مؤقتًا). أما Hooks فهي مناسبة للحالات التي تتضمن منطق الحالة فقط (مثل useMousePosition، التي تُرجع الإحداثيات فقط ولا تعرض أي واجهة مستخدم). توصي React رسميًا باستخدام Hooks.
س ماذا أفعل إذا كان عدد الخصائص (props) كبيرًا جدًّا؟
ج هذا يشير إلى أن المكون يتحمل مسؤوليات أكثر من اللازم ويجب تقسيمه. التوصية: قم بتجميع الخصائص بحيث تتوافق كل مجموعة مع مسؤوليات أحد المكونات الفرعية. على سبيل المثال، إذا كان المكون UserForm يحتوي على 12 خاصية، فقم بتقسيمه إلى ثلاثة مكونات فرعية: UserBasicInfo (الاسم، البريد الإلكتروني، رقم الهاتف)، UserAddress (المحافظة، المدينة، المنطقة، العنوان التفصيلي)، وUserSettings (الدور، الأذونات).
س أيهما أفضل، Render Props أم Hooks؟
ج Hooks هي الخيار الأفضل. كانت Render Props نمط إعادة الاستخدام المستخدم في React قبل الإصدار 16.7، وكانت تتطلب دوال استدعاء متداخلة، مما أدى إلى ما يُعرف بـ «جحيم دوال الاستدعاء». تتيح لك Hooks إعادة استخدام المنطق بـ useXxx() سطر واحد فقط من الكود، مما ينتج عنه كود أكثر بساطة وسهولة في القراءة. لا تتمتع Render Props بميزة إلا عندما يحتاج المنطق المعاد استخدامه إلى «عرض جزء من واجهة المستخدم» (مثل عندما يحتاج مكون تلميح الأداة إلى معرفة موضع عناصره الفرعية).

📖 ملخص


📝 تمارين

  1. المشكلة الأساسية (صعوبة ⭐): أنشئ مكونًا Badge يقبل count (رقمًا) وchildren (محتوى)، ويعرض رقمًا أحمر مكتوبًا في الأعلى في الزاوية اليمنى العليا.
  2. تمرين متقدم (درجة الصعوبة ⭐⭐): أنشئ مكونًا Tabs واستخدم نمط التجميع (Composite Pattern) لتنفيذ تبديل التسميات: <Tabs><TabPanel label="Tab1">inside content 1</TabPanel><TabPanel label="Tab2">inside content 2</TabPanel></Tabs>.
  3. التحدي (الصعوبة: ⭐⭐⭐): قم بإنشاء مكون Table باستخدام نمط Render Props: <Table data={users} columns={[{ title: 'Name', render: u => u.name }, { title: 'Age', render: u => u.age }]} />، مع دعم ميزة الفرز وتمييز الألوان بنمط «الزيبرا».
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%