React: العرض المشروط وعرض القوائم
آخر تحديث: 2026-10-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>
)
}
الناتج:
مقارنة جنبًا إلى جنب: صياغة HTML وصياغة JSX تعرضان ترميزًا مكافئًا. مكون React من نوع TypeScript يعرّف props عبر interface
▶ مثال: مقارنة جنبًا إلى جنب بين ثلاثة أنواع من العرض الشرطي
// ============================================
// 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>
)
}
الناتج:
إذا كان VIP: "⭐ Premium Member" + تاريخ انتهاء العضوية. وإلا: "Regular User". التوقيع: نص المستخدم أو محتوى احتياطي. العلامات تظهر فقط عند وجودها.
4. عرض القوائم: .map()
(1) الاستخدام الأساسي
Array.map() إجراء تكرار على مصفوفة وإرجاع كود JSX لكل عنصر:
(2) مصفوفة من كائنات العرض
▶ جرّب الكود(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]
الناتج:
العنوان الفرعي: " {user.isVIP ? '⭐ Premium Member' : 'Regular User'} ". يعرض: "0 && ("
| الاستراتيجية الرئيسية | الأداء | التوصية |
|---|---|---|
| المعرّف الفريد لقاعدة البيانات | ✅ الأمثل | ⭐⭐⭐ |
| سلسلة فريدة (UUID) | ✅ الأفضل | ⭐⭐⭐ |
| مؤشر المصفوفة | ⚠️ محفوف بالمخاطر (قد تصبح الحالة غير متسقة عند تغيير القائمة) | ⭐ |
| رقم عشوائي | ❌ النطاق (يختلف مع كل عملية عرض) | ❌ |
| لم يتم تمرير أي مفتاح | ❌ أداء ضعيف | ❌ |
▶ مثال: باستخدام مفتاح مقابل عدم استخدام مفتاح
▶ جرّب الكودالناتج:
قائمتان تحتويان حقول إدخال. النقر على "Randomize": ✅ القائمة (key=id) تحافظ على محاذاة قيم الإدخال؛ ❌ القائمة (key=index) تكون قيم الإدخال فيها غير محاذية للنص
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. مثال كامل: فئات المنتجات والبحث
▶ عرض المثال القابل للتشغيل (81 سطرًا)
الناتج:
يعرض: "Randomize the order". زر: Randomize the order. الإدخال: Enter content، The content may be misaligned
الناتج المتوقع: صفحة قائمة منتجات كاملة تدعم تصفية البحث، وتصفية الفئات، وإشعارات النتائج الفارغة، والإحصاءات.
▶ المثال 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>
)
}
الناتج:
نموذج شخصي: حقول Name + Email. نموذج شركة: حقول Company + Tax ID + Budget. يتم التبديل بينهما بزر. في الأسفل زر الإرسال.
الناتج:
حقل البحث يصفي العناصر. أزرار الترتيب: A-Z (حسب الاسم) / Stars (حسب التقييم). الترتيب الحالي مُبرَز بالأزرق. القائمة بعد التصفية + الترتيب تتحدث بشكل تفاعلي
▶ المثال 3: البحث في القوائم وتصفيتها وفرزها
▶ جرّب الكودالناتج:
الأطر: React ⭐220k، Vue ⭐207k، Tailwind ⭐80k، Django ⭐78k، Express ⭐63k. تصفية عبر البحث؛ تبديل الترتيب A-Z/Stars.
الناتج:
يعرض "{cat.name}"
▶ المثال 4: عرض القوائم المتداخلة — عرض المنتجات المصنفة
▶ جرّب الكودالناتج:
Electronics: Keyboard $79، Monitor $399
Furniture: Desk $249، Chair $189
Books: "No products yet" (حالة فارغة بخط مائل)
❓ أسئلة شائعة
.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ويعرض أزرار أرقام الصفحات، مع دعم الصفحتين الأولى والأخيرة بالإضافة إلى علامة الحذف.