React: العرض المشروط وعرض القوائم
آخر تحديث: 2026-08-26
العرض الشرطي يشبه إشارات المرور — فهو يحدد ما يجب عرضه بناءً على الحالة؛ أما عرض القوائم فيشبه الإنتاج على خط التجميع — قائمة بيانات تُنشئ مكونات واجهة مستخدم متطابقة على دفعات.
1. ما ستتعلمه
- استخدم المُشغِّل الثلاثي
? :للتحكم في محتوى العرض - استخدم
&&و||لتبسيط العرض الشرطي - عرض القائمة باستخدام
.map() - أهمية السمة «key» وقواعد استخدامها
- التعامل مع حالات الفراغ والتحميل
2. قصة الواجهة الأمامية لمنصة التجارة الإلكترونية
(1) المشكلة: الحالات الست لقائمة المنتجات
تعمل أليس على صفحة قائمة المنتجات لمنصة للتجارة الإلكترونية. تحتوي هذه الصفحة على ست حالات عرض، وفي كل مرة تتغير الحالة، يتعين عليها التعامل مع نموذج DOM يدويًّا:
function renderProductList(state) {
const container = document.getElementById('product-list')
container.innerHTML = ''
if (state.isLoading) {
container.innerHTML = '<div class="loading">Loading......</div>'
} else if (state.error) {
container.innerHTML = '<div class="error">Failed to load</div>'
} else if (!state.isLoggedIn) {
container.innerHTML = '<div>Please log in</div>'
} else if (state.products.length === 0) {
container.innerHTML = '<div>No products available</div>'
} else {
let html = ''
state.products.forEach(p => {
html += `<div class="card"><h3>${p.name}</h3><p>$${p.price}</p></div>`
})
container.innerHTML = html
}
}
سؤال أليس: في كل مرة تتغير فيها الحالة، أضطر إلى مسح الحاوية يدويًّا وإنشاء كود HTML. 30 سطرًا من الكود تؤدي وظيفة واحدة فقط — وهي عرض الحالة الحالية.
(2) حلول للعرض المشروط في React
function ProductList({ user, products, isLoading, error }) {
// Each state is independent if Branch,No interference with one another
if (isLoading) return <LoadingSpinner />
if (error) return <ErrorMessage message={error} />
if (!user) return <LoginPrompt />
if (products.length === 0) return <EmptyState />
return (
<div className="product-grid">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}
المزايا: 15 سطراً فقط من التعليمات البرمجية تغطي جميع الحالات، كما أن كل فرع يسهل فهمه بنظرة واحدة.
3. ثلاث طرق لاستخدام العرض الشرطي
| الطريقة | الصيغة | حالات الاستخدام | ملاحظات |
|---|---|---|---|
| عامل ثلاثي | condition ? A : B |
اختر أحد الخيارين (مع فرع "else") | يصعب قراءة العوامل الثلاثية المتداخلة؛ تجنب استخدام أكثر من مستويين |
المنطق و&& |
condition && A |
لا تُعرض إلا إذا تم استيفاء الشرط (بدون "else") | 0 && <Comp /> ستُعرض بقيمة 0؛ استخدم > 0 |
| «أو» المنطقي ` | ` | `value |
(1) عامل ثلاثي ? :
السيناريوهات المناسبة لـ«اختيار أحد خيارين»:
function StatusBadge({ isActive }) {
return (
<span style={{ color: isActive ? '#52c41a' : '#ff4d4f' }}>
{isActive ? '🟢 Online' : '🔴 Offline'}
</span>
)
}
// isActive=true → <span>🟢 Online</span>
// isActive=false → <span>🔴 Offline</span>
(2) المنطق و&&
مناسب لعبارة «لا تُعرض إلا إذا استوفيت الشروط؛ وإلا فلا تُعرض»:
function Notification({ count }) {
return (
<div className="notification-icon">
🔔
{/* count > 0 A red dot appears,Otherwise, nothing will be displayed. */}
{count > 0 && (
<span className="badge">{count > 99 ? '99+' : count}</span>
)}
</div>
)
}
// count=0 → Show only 🔔
// count=5 → Show 🔔 and "5"
{items.length && <List />}. عندما تكون قيمة items.length هي 0، تكون نتيجة 0 && <List /> هي 0، وسيقوم React بعرض الرقم 0 على الصفحة. الطريقة الصحيحة لكتابة ذلك هي {items.length > 0 && <List />}.
(3) «أو» المنطقي ||
مناسب لـ«توفير قيمة بديلة»:
function UserProfile({ user }) {
return (
<div>
<h2>{user.nickname || 'Anonymous User'}</h2>
<p>{user.bio || 'This person is lazy.,Nothing was written...'}</p>
<p>Location:{user.location || 'Unknown'}</p>
</div>
)
}
▶ مثال: مقارنة جنبًا إلى جنب بين ثلاثة أنواع من العرض الشرطي
// ============================================
// Example:Combined Use of Three Types of Conditional Rendering in the User Panel
// ============================================
function UserPanel({ user }) {
return (
<div className="panel">
{/* 1. Ternary:Choose one of the two */}
<h2>
{user.isVIP ? '⭐ Premium Member' : 'Regular User'}
</h2>
{/* 2. &&:Display only when met */}
{user.isVIP && (
<p>Membership Expiration:{user.vipExpireDate}</p>
)}
{/* 3. ||:Floor value */}
<p>Signature:{user.slogan || 'This person is really cool.,No signature'}</p>
{/* Combined Use:List of Tags */}
{user.tags && user.tags.length > 0 && (
<div>
{user.tags.map(tag => (
<span key={tag} style={{ background: '#f0f0f0', padding: '2px 8px', margin: '2px', borderRadius: '4px' }}>
{tag}
</span>
))}
</div>
)}
</div>
)
}
4. عرض القوائم: .map()
(1) الاستخدام الأساسي
Array.map() إجراء تكرار على مصفوفة وإرجاع كود JSX لكل عنصر:
const fruits = ['Apple', 'Banana', 'Orange', 'Grapes']
function FruitList() {
return (
<ul>
{fruits.map(fruit => (
<li>{fruit}</li>
))}
</ul>
)
}
// Output:
// <ul>
// <li>Apple</li>
// <li>Banana</li>
// <li>Orange</li>
// <li>Grapes</li>
// </ul>
(2) مصفوفة من كائنات العرض
function ScoreTable() {
const students = [
{ id: 1, name: 'Alice', score: 92 },
{ id: 2, name: 'Bob', score: 85 },
{ id: 3, name: 'Charlie', score: 78 }
]
return (
<table border="1" cellPadding="8">
<thead>
<tr><th>Name</th><th>Fractions</th><th>Level</th></tr>
</thead>
<tbody>
{students.map(s => (
<tr key={s.id}>
<td>{s.name}</td>
<td>{s.score}</td>
<td>{s.score >= 90 ? 'Excellent' : s.score >= 80 ? 'Good' : 'Passing Grade'}</td>
</tr>
))}
</tbody>
</table>
)
}
(3) 3 تقنيات لتحويل البيانات من أجل عرض القوائم
const items = [
{ id: 1, name: 'React Basics', price: 0 },
{ id: 2, name: 'Vue Getting Started', price: 0 },
{ id: 3, name: 'Node.js Real-World Experience', price: 99 }
]
// 1. Direct Rendering
{items.map(item => <li key={item.id}>{item.name}</li>)}
// 2. Filter First, Then Render(Free Courses)
{items
.filter(item => item.price === 0)
.map(item => <li key={item.id}>{item.name}(Free)</li>)
}
// 3. Extract Rendering Functions
function renderItem(item) {
return (
<li key={item.id}>
{item.name} - ${item.price}
</li>
)
}
{items.map(renderItem)}
5. أهمية المفتاح
يستخدم React key لتعريف كل عنصر في القائمة. عندما يظل المفتاح ثابتًا، يعيد React استخدام عقدة DOM؛ وعندما يتغير المفتاح، يقوم React بإتلافها وإعادة إنشائها.
graph LR
A[List Changes] --> B{key Changes?}
B -->|No, key The same| C[Reuse DOM Node<br/>Update only the changes]
B -->|Yes, key It's changed| D[Delete Old Nodes<br/>Create a New Node]
B -->|No, None key| E[Render All Over]
| الاستراتيجية الرئيسية | الأداء | التوصية |
|---|---|---|
| المعرّف الفريد لقاعدة البيانات | ✅ الأمثل | ⭐⭐⭐ |
| سلسلة فريدة (UUID) | ✅ الأفضل | ⭐⭐⭐ |
| مؤشر المصفوفة | ⚠️ محفوف بالمخاطر (قد تصبح الحالة غير متسقة عند تغيير القائمة) | ⭐ |
| رقم عشوائي | ❌ النطاق (يختلف مع كل عملية عرض) | ❌ |
| لم يتم تمرير أي مفتاح | ❌ أداء ضعيف | ❌ |
▶ مثال: باستخدام مفتاح مقابل عدم استخدام مفتاح
// ============================================
// Example:key Impact on the Input Field's State
// ============================================
function KeyDemo() {
const [items, setItems] = React.useState([
{ id: 'a', text: 'Task A' },
{ id: 'b', text: 'Task B' },
{ id: 'c', text: 'Task C' }
])
function shuffle() {
setItems([...items].sort(() => Math.random() - 0.5))
}
return (
<div>
<button onClick={shuffle}>Randomize the order</button>
<p>✅ has key(id Stable,The content in the input field remains intact):</p>
<ul>
{items.map(item => (
<li key={item.id}>
{item.text} <input placeholder="Enter content" />
</li>
))}
</ul>
<p>❌ Without key(or index As key,The text in the input field appears jumbled):</p>
<ul>
{items.map((item, index) => (
<li key={index}>
{item.text} <input placeholder="The content may be misaligned" />
</li>
))}
</ul>
</div>
)
}
// Click"Randomize the order"after ,has key The content in the list input field remains in order
// use index List of,The content in the input field and the text are misaligned
6. حالة الفراغ وحالة التحميل
| الحالة | الشرط | سلوك واجهة المستخدم | كيفية التعامل |
|---|---|---|---|
| قيد التحميل | loading === true |
شاشة دوارة / شاشة هيكلية | if (loading) return <Spinner /> |
| فشل التحميل | error !== null |
رسالة خطأ + زر «إعادة المحاولة» | if (error) return <ErrorView /> |
| بيانات فارغة | data.length === 0 |
رسم توضيحي للحالة الفارغة + نص | if (!data.length) return <Empty /> |
| عادي | لا شيء مما سبق | عرض قائمة البيانات | الافتراضي: العودة إلى واجهة المستخدم العادية |
في المشاريع العملية، هناك ثلاث حالات يجب التعامل معها عند تحميل البيانات:
// ============================================
// Example:Complete List Component(3 Handling Different States)
// ============================================
function ArticleList() {
const [articles, setArticles] = React.useState([])
const [isLoading, setIsLoading] = React.useState(true)
const [error, setError] = React.useState(null)
// Simulated Loading(Actually, it is API Call)
React.useEffect(() => {
fetch('/api/articles')
.then(res => {
if (!res.ok) throw new Error('Network request failed')
return res.json()
})
.then(data => {
setArticles(data)
setIsLoading(false)
})
.catch(err => {
setError(err.message)
setIsLoading(false)
})
}, [])
// 1. Loading...
if (isLoading) {
return (
<div style={{ textAlign: 'center', padding: '40px' }}>
<div className="spinner" />
<p>Loading article...</p>
</div>
)
}
// 2. Failed to load
if (error) {
return (
<div style={{ textAlign: 'center', padding: '40px', color: '#ff4d4f' }}>
<p>❌ Failed to load:{error}</p>
<button onClick={() => window.location.reload()}>Reload</button>
</div>
)
}
// 3. Empty data
if (articles.length === 0) {
return (
<div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
<p>📭 No articles available</p>
<p>No one has posted an article yet.,Go ahead and write your first one!!</p>
</div>
)
}
// 4. Normal Rendering
return (
<div>
<h3>List of Articles(Total {articles.length} articles)</h3>
{articles.map(article => (
<ArticleCard key={article.id} article={article} />
))}
</div>
)
}
7. مثال كامل: فئات المنتجات والبحث
// ============================================
// Complete Example:Product List(Conditional Rendering + Comprehensive Guide to List Rendering)
// Features:Filter by Category、Search、Prompt for empty results
// ============================================
function ShopPage() {
const [products] = React.useState([
{ id: 1, name: 'Wireless Mouse', category: 'Electronics', price: 89, sales: 2000 },
{ id: 2, name: 'Mechanical Keyboard', category: 'Electronics', price: 299, sales: 1500 },
{ id: 3, name: 'Notebook', category: 'Office', price: 15, sales: 5000 },
{ id: 4, name: 'Office Chair', category: 'Furniture', price: 899, sales: 800 },
{ id: 5, name: 'Desk Lamp', category: 'Furniture', price: 129, sales: 3000 },
{ id: 6, name: 'Monitor', category: 'Electronics', price: 1599, sales: 600 }
])
const [search, setSearch] = React.useState('')
const [category, setCategory] = React.useState('All')
// Filtering Logic
const filtered = products.filter(p => {
const matchSearch = p.name.includes(search)
const matchCategory = category === 'All' || p.category === category
return matchSearch && matchCategory
})
// Get the list of categories(Remove duplicates)
const categories = ['All', ...new Set(products.map(p => p.category))]
return (
<div style={{ maxWidth: '800px', margin: '0 auto' }}>
<h2>🛒 Product List</h2>
{/* Search box */}
<input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search by Product Name..."
style={{ width: '100%', padding: '8px', marginBottom: '12px', border: '1px solid #d9d9d9', borderRadius: '4px' }}
/>
{/* Category Button(List Rendering) */}
<div style={{ marginBottom: '12px' }}>
{categories.map(cat => (
<button
key={cat}
onClick={() => setCategory(cat)}
style={{
padding: '6px 16px', margin: '0 4px',
backgroundColor: category === cat ? '#1890ff' : '#f0f0f0',
color: category === cat ? 'white' : '#333',
border: 'none', borderRadius: '4px', cursor: 'pointer'
}}
>
{cat}
</button>
))}
</div>
{/* Results Summary */}
{search || category !== 'All' ? (
<p style={{ color: '#666' }}>
{search && `Search"${search}" `}
{category !== 'All' && `/${category} `}
Total {filtered.length} Items
</p>
) : null}
{/* Empty result */}
{filtered.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
<p>😕 No matching products were found.</p>
<button
onClick={() => { setSearch(''); setCategory('All') }}
style={{ padding: '8px 16px', marginTop: '8px', cursor: 'pointer' }}
>
Clear All Filters
</button>
</div>
) : (
/* Product Grid(List Rendering) */
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px' }}>
{filtered.map(product => (
<div key={product.id} style={{ border: '1px solid #eee', borderRadius: '8px', padding: '16px' }}>
<div style={{ height: '120px', backgroundColor: '#f5f5f5', borderRadius: '4px', marginBottom: '8px' }} />
<h4>{product.name}</h4>
<p style={{ color: '#ff4d4f', fontWeight: 'bold', fontSize: '18px' }}>${product.price}</p>
<p style={{ color: '#999', fontSize: '13px' }}>Sold {product.sales} items</p>
<span style={{ background: '#f0f5ff', color: '#1890ff', padding: '2px 8px', borderRadius: '4px', fontSize: '12px' }}>
{product.category}
</span>
</div>
))}
</div>
)}
</div>
)
}
الناتج المتوقع: صفحة قائمة منتجات كاملة تدعم تصفية البحث، وتصفية الفئات، وإشعارات النتائج الفارغة، والإحصاءات.
▶ المثال 5: عرض حقول النموذج ديناميكيًا
function DynamicForm() {
const [formType, setFormType] = useState('personal')
const [formData, setFormData] = useState({
name: '',
email: '',
company: '',
taxId: '',
budget: '',
})
function handleChange(e) {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
}
const personalFields = (
<>
<input name="name" value={formData.name} onChange={handleChange} placeholder="Name" />
<input name="email" value={formData.email} onChange={handleChange} placeholder="Email" />
</>
)
const businessFields = (
<>
<input name="company" value={formData.company} onChange={handleChange} placeholder="Company" />
<input name="taxId" value={formData.taxId} onChange={handleChange} placeholder="Tax ID" />
<input name="budget" value={formData.budget} onChange={handleChange} placeholder="Budget ($)" />
</>
)
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<h3>Registration</h3>
<div style={{ marginBottom: 12 }}>
{['personal', 'business'].map(type => (
<button
key={type}
onClick={() => setFormType(type)}
style={{
padding: '6px 16px', marginRight: 8, cursor: 'pointer',
backgroundColor: formType === type ? '#1890ff' : '#f0f0f0',
color: formType === type ? 'white' : '#333', border: 'none', borderRadius: 4,
}}
>
{type === 'personal' ? 'Personal' : 'Business'}
</button>
))}
</div>
{formType === 'personal' ? personalFields : businessFields}
<button style={{ marginTop: 12, padding: '8px 24px', cursor: 'pointer' }}>Submit</button>
</div>
)
}
▶ المثال 3: البحث في القوائم وتصفيتها وفرزها
function FilterableList() {
const [items] = useState([
{ id: 1, name: 'React', category: 'Frontend', stars: 220 },
{ id: 2, name: 'Django', category: 'Backend', stars: 78 },
{ id: 3, name: 'Vue', category: 'Frontend', stars: 207 },
{ id: 4, name: 'Express', category: 'Backend', stars: 63 },
{ id: 5, name: 'Tailwind', category: 'CSS', stars: 80 },
])
const [search, setSearch] = useState('')
const [sortKey, setSortKey] = useState('name')
const filtered = items
.filter(i => i.name.toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => sortKey === 'stars' ? b.stars - a.stars : a.name.localeCompare(b.name))
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<input value={search} onChange={e => setSearch(e.target.value)}
placeholder="Search frameworks..." style={{ width: '100%', padding: 8, borderRadius: 4, marginBottom: 8 }} />
<div style={{ marginBottom: 8 }}>
<button onClick={() => setSortKey('name')} style={{ padding: '4px 12px', marginRight: 4, cursor: 'pointer', background: sortKey === 'name' ? '#1890ff' : '#f0f0f0', color: sortKey === 'name' ? 'white' : '#333', border: 'none', borderRadius: 4 }}>A-Z</button>
<button onClick={() => setSortKey('stars')} style={{ padding: '4px 12px', cursor: 'pointer', background: sortKey === 'stars' ? '#1890ff' : '#f0f0f0', color: sortKey === 'stars' ? 'white' : '#333', border: 'none', borderRadius: 4 }}>Stars</button>
</div>
{filtered.length === 0 ? (
<p style={{ color: '#999', textAlign: 'center' }}>No results</p>
) : (
<ul style={{ listStyle: 'none', padding: 0 }}>
{filtered.map(i => <li key={i.id} style={{ padding: 8, borderBottom: '1px solid #f0f0f0', display: 'flex', justifyContent: 'space-between' }}><span>{i.name} <small style={{ color: '#999' }}>({i.category})</small></span><span>⭐ {i.stars}k</span></li>)}
</ul>
)}
</div>
)
}
▶ المثال 4: عرض القوائم المتداخلة — عرض المنتجات المصنفة
function CategorizedProducts() {
const categories = [
{ id: 'electronics', name: 'Electronics', products: [
{ id: 1, name: 'Keyboard', price: 79 },
{ id: 2, name: 'Monitor', price: 399 },
]},
{ id: 'furniture', name: 'Furniture', products: [
{ id: 3, name: 'Desk', price: 249 },
{ id: 4, name: 'Chair', price: 189 },
]},
{ id: 'books', name: 'Books', products: [] },
]
return (
<div style={{ maxWidth: 500, margin: '0 auto' }}>
{categories.map(cat => (
<div key={cat.id} style={{ marginBottom: 16 }}>
<h3 style={{ borderBottom: '2px solid #1890ff', paddingBottom: 4 }}>{cat.name}</h3>
{cat.products.length === 0 ? (
<p style={{ color: '#999', fontStyle: 'italic', paddingLeft: 16 }}>No products yet</p>
) : (
<ul style={{ listStyle: 'none', padding: 0, paddingLeft: 16 }}>
{cat.products.map(p => (
<li key={p.id} style={{ padding: 4, display: 'flex', justifyContent: 'space-between' }}>
<span>{p.name}</span><span>${p.price}</span>
</li>
))}
</ul>
)}
</div>
))}
</div>
)
}
❓ أسئلة شائعة
.map()؟ كيف يتم التعامل مع المفاتيح عند استخدام عدة استدعاءات لـ .map()؟.map() مفتاحه الخاص. على سبيل المثال، عرض "الفئة → المنتج: cats.map(c => <div key={c.id}><h3>{c.name}</h3>{c.products.map(p => <span key={p.id}>{p.name}</span>)}</div>)". يمكن أن تكون المفاتيح في المستويات المختلفة متطابقة (شريطة أن تكون فريدة داخل المصفوفات الخاصة بها).&& مناسبة لـ «العرض إذا تم استيفاء الشرط؛ وعدم عرض أي شيء إذا لم يتم استيفاؤه»؛ أما التعبير الثلاثي ? : فهو مناسب لـ «عرض المكون X إذا تم استيفاء الشرط A؛ وعرض المكون Y إذا تم استيفاء الشرط B». إذا كنت تريد «عدم عرض أي شيء عند عدم استيفاء الشرط» باستخدام العامل الثلاثي، فستكون صيغة الكتابة condition ? <Comp /> : null، لكن && أكثر إيجازًا في هذه الحالة. ومع ذلك، انتبه إلى «فخ عرض الرقم 0» مع && — حيث ستعرض 0 && <Comp /> الرقم 0.📖 ملخص
- هناك ثلاثة أنواع من العرض الشرطي:
? :(إما/أو)، و&&(العرض فقط)، و||(القيمة الاحتياطية) - استخدم
.map()لعرض القائمة؛ يجب ربط كل عنصر JSX بـkeyفريد - يجب أن تتعامل المشاريع الواقعية مع ثلاث حالات: التحميل، وفشل التحميل، وعدم وجود بيانات
- احذر من فخ العرض 0 الخاص بـ
{items.length && <List />} - استخدم معرّفًا فريدًا وثابتًا كمفتاح؛ ولا تستخدم فهرسًا
📝 تمارين
- المشكلة الأساسية (صعوبة ⭐): أنشئ مكونًا
TemperatureDisplayيعرض، استنادًا إلى خصائصtemp، رمز 🔥 باللون الأحمر عندما تكون درجة الحرارة >35 درجة مئوية، ورمز ☀️ باللون الأصفر عندما تكون درجة الحرارة بين 15 و35 درجة مئوية، ورمز ❄️ باللون الأزرق عندما تكون درجة الحرارة <15 درجة مئوية. - مشكلة متقدمة (درجة الصعوبة ⭐⭐): أنشئ مكونًا
TodoFilterيقبل خاصيتين —todos(مصفوفة من المهام) وfilter(كلها/مكتملة/غير مكتملة) — وقم بتنفيذ عملية التصفية بناءً على الحالة. - التحدي (الصعوبة: ⭐⭐⭐): أنشئ مكونًا
Paginationيقبل الخصائصtotalPagesوcurrentPageوonPageChangeويعرض أزرار أرقام الصفحات، مع دعم الصفحتين الأولى والأخيرة بالإضافة إلى علامة الحذف.