Vue.js: العرض الشرطي والقوائم
آخر تحديث: 2026-08-26
يُعد العرض الشرطي (v-if / v-show) وعرض القوائم (v-for) أكثر التوجيهات استخدامًا في قوالب Vue. يتحكم الأول في ما إذا كان العنصر سيُعرض أم لا، بينما يتحكم الثاني في عدد العناصر التي يتم عرضها. ويمكن لهذين التوجيهين معًا التعامل مع 90% من سيناريوهات واجهة المستخدم.
يُعد فهم الاختلافات الخمسة الرئيسية بين v-if وv-for، بالإضافة إلى الاستخدام الصحيح لـ :key، أمرًا ضروريًا لتطوير تطبيقات Vue عالية الأداء.
1. ما ستتعلمه
العرض المشروط
- الفروق الخمسة الرئيسية بين
v-showوv-if v-forالتكرار على المصفوفات والكائنات والأرقام والسلاسل- الغرض من
:keyوأفضل الممارسات المتعلقة به templateتغليف عناصر متعددة- تصفية القوائم وفرزها وتقسيمها إلى صفحات
- تحسين التمرير الافتراضي (أكثر من 10,000 عنصر)
2. كابوس البرمجة: «عبارات if المتداخلة» في لوحة المعلومات
(1) المشكلة: 5 علامات v-if متداخلة — كابوس في عملية تصحيح الأخطاء
خرج كود لوحة التحكم الخاصة بأليس عن السيطرة:
<!-- ❌ The "Broken" Version:v-if Nested Hell -->
<template>
<div v-if="user">
<div v-if="user.isAdmin">
<div v-if="orders.length > 0">
<div v-if="!isLoading">
<div v-if="!hasError">
<!-- Content is finally displayed -->
<p>Orders: {{ orders.length }}</p>
</div>
<div v-else>Error: {{ errorMsg }}</div>
</div>
<div v-else>Loading...</div>
</div>
<div v-else>No orders</div>
</div>
<div v-else>Not admin</div>
</div>
<div v-else>Please login</div>
</template>
مراجعة الكود من تشارلي:
"أليس، هذا غير قابل للقراءة. هناك 5 مستويات من التداخل في عبارات v-if. ماذا لو أضفنا شرطين آخرين؟ نحن بحاجة إلى نمط أكثر بساطة."
(2) حل Vue: التسوية باستخدام v-if + الخصائص المحسوبة
<template>
<div v-if="!user">Please login</div>
<div v-else-if="!user.isAdmin">Not admin</div>
<div v-else-if="isLoading">Loading...</div>
<div v-else-if="hasError">Error: {{ errorMsg }}</div>
<div v-else-if="orders.length === 0">No orders</div>
<div v-else>
<p>Orders: {{ orders.length }}</p>
<OrderList :orders="orders" />
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(['user', 'orders', 'isLoading', 'hasError', 'errorMsg'])
// Flatten 5 conditions (v-else-if chain)
</script>
(3) الإيرادات
- سهولة القراءة: 5 مستويات من التداخل → 6 أسطر مرتبة جنبًا إلى جنب
- قابلية الصيانة: لإضافة شرط، ما عليك سوى إضافة سطر واحد من
v-else-if - تصحيح الأخطاء: لكل سطر شرطه الخاص، مما يسهل تعيين نقاط التوقف
3. العرض الشرطي باستخدام v-if / v-else-if / v-else
(1) قواعد النحو الأساسية
<template>
<!-- Single condition -->
<p v-if="isVisible">Visible</p>
<!-- Choose one of the two -->
<p v-if="score >= 60">Passed</p>
<p v-else>Failed</p>
<!-- Choose one or more -->
<p v-if="type === 'A'">Type A</p>
<p v-else-if="type === 'B'">Type B</p>
<p v-else-if="type === 'C'">Type C</p>
<p v-else>Unknown</p>
</template>
(2) العنصر template
<template>
<!-- template:Grouping Multiple Elements,Do not render DOM -->
<template v-if="isAdmin">
<h1>Admin Dashboard</h1>
<p>Welcome, {{ user.name }}</p>
<button>Edit</button>
</template>
<!-- Does not render template element (Savings 1 DOM layer) -->
</template>
(3) 5 حالات استخدام رئيسية
| السيناريو | استخدام v-if |
|---|---|
| مسجل الدخول/غير مسجل الدخول | ✅ |
| قيد التحميل.../تم التحميل | ✅ |
| لا توجد بيانات/تتوفر بيانات | ✅ |
| status الخطأ | ✅ |
| التبديل المتكرر بين إظهار/إخفاء | ❌ (استخدم v-show) |
4. 5 اختلافات رئيسية بين v-show و v-if
(1) الاختلافات الرئيسية
<template>
<!-- v-if: When conditions are false, Elements removed from DOM -->
<p v-if="show">v-if</p>
<!-- show=false: The element does not exist in DOM -->
<!-- v-show: When conditions are false, display: none -->
<p v-show="show">v-show</p>
<!-- show=false: <p style="display: none">v-show</p> -->
</template>
(2) 5 مقارنات رئيسية
| الجانب | v-if | v-show |
|---|---|---|
| التعامل مع DOM | إزالة العنصر عندما تكون الstatus غير صحيحة | يبقى دائمًا في DOM؛ ويتم فقط تبديل status الخاصية display |
| أداء التبديل | ضعيف (إزالة وإعادة إنشاء في كل مرة) | جيد (CSS فقط) |
| العرض الأولي | لا يتم العرض إذا كان الشرط غير صحيح | يتم العرض دائمًا (حتى لو كان غير مرئي) |
| التوافق مع الانتقالات | ✅ مثالي (حركات الدخول/الخروج) | ❌ غير مدعوم |
| حالات الاستخدام | التبديل من حين لآخر | التبديل المتكرر |
(3) توصيات الاختيار
// Switch Occasionally(If you are logged in/Not logged in)→ v-if
<div v-if="isLoggedIn">Welcome, {{ user.name }}</div>
<div v-else>Please login</div>
// Frequent switching (e.g. tab switch) → v-show
<button v-show="isActive">Active</button>
<button v-show="!isActive">Inactive</button>
5. عرض القوائم باستخدام v-for
(1) التكرار عبر array
<template>
<!-- Basic Iteration -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- Indexed -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' }
])
</script>
(2) التكرار على الكائنات
<template>
<ul>
<li v-for="(value, key, index) in user" :key="key">
{{ index + 1 }}. {{ key }}: {{ value }}
</li>
</ul>
</template>
<script setup>
import { reactive } from 'vue'
const user = reactive({
name: 'Alice',
age: 25,
email: 'alice@example.com'
})
// Output:1. name: Alice 2. age: 25 3. email: alice@example.com
</script>
(3) التكرار عبر الأرقام
<template>
<!-- Rendering 1 to 10 -->
<span v-for="n in 10" :key="n">{{ n }}</span>
<!-- Output:12345678910 -->
</template>
(4) التكرار عبر سلسلة أحرف
<template>
<!-- One character per line span -->
<span v-for="char in 'Hello'" :key="char">{{ char }}</span>
<!-- Output:H e l l o -->
</template>
6. الغرض من :key وأفضل الممارسات
(1) ما هو :key؟
:key قم بتعيين معرّف فريد لكل عنصر من عناصر v-for لمساعدة Vue على تحديث DOM بكفاءة.
<template>
<!-- ❌ None :key(Vue Use by default index) -->
<li v-for="item in item">{{ item.name }}</li>
<!-- ✅ Has :key (Use "unique" id) -->
<li v-for="item in item" :key="item.id">{{ item.name }}</li>
</template>
(2) لماذا يُعد :key ضروريًا؟
graph LR A[Original List A B C] --> B[New List B C D] B --> C[None key:Possible misalignment] B --> D[Has key: Update Correctly] style C fill:#ff6b6b style D fill:#42b883
عندما لا يكون هناك :key:
- مقارنة استخدام
indexفي Vue - بعد إزالة A، يصبح B/C (0)/(1)، لكن Vue تعتقد أن «B/C لا يزال موجودًا»
- يؤدي إلى عدم توافق status المكونات
عندما يكون :key موجودًا:
- مقارنة المعرّفات في Vue
- تحديد العناصر التي تمت إضافتها أو حذفها أو نقلها بدقة
- الأداء الأمثل
(3) أفضل الممارسات لـ :key
<!-- ✅ Recommendations:The Only One ID -->
<li v-for="user in users" :key="user.id">
<!-- ✅ Second choice:Unique Field(Email,Mobile phone number, etc.)-->
<li v-for="user in users" :key="user.email">
<!-- ❌ Avoid:index(When the data changes, the alignment is off.)-->
<li v-for="(user, index) in users" :key="index">
<!-- ❌ Never, under any circumstances:random()(Every render is different,Forced Rebuild)-->
<li v-for="user in users" :key="Math.random()">
7. تصفية القوائم / الفرز / تقسيم الصفحات
(1) التصفية + الفرز (الخصائص المحسوبة)
import { ref, computed } from 'vue'
const items = ref([
{ id: 1, name: 'iPhone', price: 999, category: 'phone' },
{ id: 2, name: 'MacBook', price: 2499, category: 'laptop' },
{ id: 3, name: 'iPad', price: 599, category: 'tablet' }
])
const searchQuery = ref('')
const selectedCategory = ref('all')
const sortBy = ref('price') // 'price' / 'name'
// Filter + Sort(computed Automatic Caching)
const filteredItems = computed(() => {
let result = items.value
// 1. Filter by search term
if (searchQuery.value) {
result = result.filter(item =>
item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
}
// 2. Filter by Category
if (selectedCategory.value !== 'all') {
result = result.filter(item => item.category === selectedCategory.value)
}
// 3. Sort
result = [...result].sort((a, b) => {
if (sortBy.value === 'price') return a.price - b.price
if (sortBy.value === 'name') return a.name.localeCompare(b.name)
return 0
})
return result
})
(2) ترقيم الصفحات
const currentPage = ref(1)
const pageSize = ref(10)
// Data after pagination
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return filteredItems.value.slice(start, start + pageSize.value)
})
// Total Number of Pages
const totalPages = computed(() =>
Math.ceil(filteredItems.value.length / pageSize.value)
)
8. مثال كامل: قائمة المنتجات الديناميكية
▶ مثال: 1. 5 طرق لاستخدام v-if/v-show
<template>
<!-- 1. v-if:Conditional Rendering(Not here DOM) -->
<p v-if="show">v-if</p>
<!-- 2. v-show:display none(Always there DOM) -->
<p v-show="show">v-show</p>
<!-- 3. v-else-if:Chained Conditions -->
<p v-if="type === 'A'">A</p>
<p v-else-if="type === 'B'">B</p>
<p v-else>C</p>
<!-- 4. v-if + template:Multi-element -->
<template v-if="isAdmin">
<h1>Admin</h1>
<button>Edit</button>
</template>
<!-- 5. v-if vs v-show Performance Comparison -->
<div v-if="occasionally">Switch Occasionally</div>
<div v-show="frequently">Frequent switching(Not recommended for use in combination with `<transition>`,v-if That's the only way)</div>
</template>
▶ مثال: 2. 5 طرق لاستخدام v-for
<template>
<!-- 1. Array -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
<!-- 2. Array + Index -->
<li v-for="(item, i) in items" :key="item.id">
{{ i + 1 }}. {{ item.name }}
</li>
<!-- 3. Object -->
<li v-for="(value, key) in user" :key="key">
{{ key }}: {{ value }}
</li>
<!-- 4. Numbers -->
<span v-for="n in 5" :key="n">{{ n }}</span>
<!-- 5. String -->
<span v-for="char in 'Hello'" :key="char">{{ char }}</span>
</template>
▶ مثال: 3. مقارنة بين 5 طرق لاستخدام :key
<template>
<!-- 1. The Only One ID(Best) -->
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
<!-- 2. Unique Field -->
<li v-for="user in users" :key="user.email">{{ user.name }}</li>
<!-- 3. Composite key -->
<li v-for="post in posts" :key="`${post.userId}-${post.id}`">
{{ post.title }}
</li>
<!-- 4. Use index (Second choice) -->
<li v-for="(item, i) in items" :key="i">{{ item.name }}</li>
<!-- 5. ❌ random()(Never, under any circumstances) -->
<li v-for="item in items" :key="Math.random()">{{ item.name }}</li>
</template>
▶ مثال: 4. مثال كامل على تصفية القوائم + الفرز + تقسيم الصفحات
<template>
<div>
<!-- Search box -->
<input v-model="searchQuery" placeholder="Search...">
<!-- Filter by Category -->
<select v-model="selectedCategory">
<option value="all">All</option>
<option value="phone">Phone</option>
<option value="laptop">Laptop</option>
</select>
<!-- Sort -->
<select v-model="sortBy">
<option value="price">Price</option>
<option value="name">Name</option>
</select>
<!-- List -->
<ul>
<li v-for="item in paginatedItems" :key="item.id">
{{ item.name }} - ${{ item.price }}
</li>
</ul>
<!-- Pagination -->
<button :disabled="currentPage === 1" @click="currentPage--">
Previous
</button>
<span>{{ currentPage }} / {{ totalPages }}</span>
<button :disabled="currentPage === totalPages" @click="currentPage++">
Next
</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const item = ref([
{ id: 1, name: 'iPhone', price: 999, category: 'phone' },
{ id: 2, name: 'MacBook', price: 2499, category: 'laptop' },
{ id: 3, name: 'iPad', price: 599, category: 'tablet' }
])
const searchQuery = ref('')
const selectedCategory = ref('all')
const sortBy = ref('price')
const currentPage = ref(1)
const pageSize = 10
const filteredItems = computed(() => {
let result = item.value
if (searchQuery.value) {
result = result.filter(i => i.name.includes(searchQuery.value))
}
if (selectedCategory.value !== 'all') {
result = result.filter(i => i.category === selectedCategory.value)
}
return [...result].sort((a, b) => {
if (sortBy.value === 'price') return a.price - b.price
return a.name.localeCompare(b.name)
})
})
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * pageSize
return filteredItems.value.slice(start, start + pageSize)
})
const totalPages = computed(() =>
Math.ceil(filteredItems.value.length / pageSize)
)
</script>
▶ مثال: 5. 5 أخطاء شائعة
| الخطأ | الأعراض | الحل |
|---|---|---|
| لا شيء :key | عدم توافق القائمة، ضعف الأداء | إضافة :key="item.id" |
| استخدام v-for و v-if معًا | الالتباس بشأن أولوية v-if | التغليف باستخدام template أو استخدام computed |
| استخدام «index» كمفتاح | عدم التوافق أثناء الفرز | استخدام معرّف فريد |
لا يدعم v-show <transition> |
لا يمكن تحريكه | استخدم v-if بدلاً من ذلك + <transition> |
| القائمة كبيرة جدًا (أكثر من 100,000) | تتجمد الصفحة | استخدم vue-virtual-scroller للتمرير الافتراضي |
▶ مثال: 6. أولوية v-if و v-for
<!-- In Vue 3, v-if has higher priority than v-for (Vue 2 is the opposite) -->
<ul>
<!-- Vue 3: First determine v-if, then v-for -->
<li v-for="user in users" v-if="user.isActive" :key="user.id">
{{ user.name }}
</li>
<!-- ✅ But Vue officially recommends using template to separate -->
<!-- ❌ Incorrect wording(Not recommended) -->
<li v-for="user in users" v-if="shouldShowUsers(user)" :key="user.id">
{{ user.name }}
</li>
<!-- ✅ Recommendations:computed Filter -->
<li v-for="user in activeUsers" :key="user.id">
{{ user.name }}
</li>
</template>
❓ أسئلة شائعة
:key مطلوب مع v-for؟:key لتجنب مشاكل الأداء وعدم تطابق حالات المكونات. يُفضل استخدام معرّف فريد (item.id)، يليه index؛ ولا تستخدم random() أبدًا.template أو التصفية باستخدام computed.template؟template هو عنصر غلاف منطقي في Vue لا يتم عرضه في DOM. ويُستخدم لتجنب ظهور عناصر div غير الضرورية عندما يتضمن v-if أو v-for عناصر متعددة.:key؛ (2) استخدم computed لتخزين النتائج المؤقتة في cache لتصفية/فرزها؛ (3) بالنسبة للقوائم الكبيرة (100,000+)، استخدم vue-virtual-scroller للتمرير الافتراضي لعرض المنطقة المرئية فقط.v-for؟v-for="(value, key, index) in obj"، ترتيب المعلمات: القيمة، المفتاح، الفهرس.<div v-if="item.length">Column </div><div v-else> Data</div>.activeIndex إلى كل عنصر: @click="activeIndex = item.id" :classe="{ active: activeIndex === item.id }".📖 ملخص
- العرض الشرطي باستخدام v-if (يُزيل العنصر من DOM)، بينما تعمل v-show على تبديل status الخاصية
display(تظل دائمًا موجودة في DOM) - استخدم
v-ifللتغييرات العرضية وv-showللتغييرات المتكررة - استخدم
v-forللتكرار عبر المصفوفات والكائنات والأرقام والسلاسل؛ واستخدم:keyلتحسين الأداء - :key: يُفضل استخدام معرّف فريد؛ ويُعد الفهرس الخيار الثاني الأفضل؛ ولا تستخدم الدالة random()
- العنصر
templateيضم عدة عناصر ولا يتم عرضه في DOM - تصفية قائمة cache وفرزها وتقسيمها إلى صفحات تلقائيًا باستخدام
computed - في Vue 3،
v-ifله الأسبقية علىv-for - تحسين عرض القوائم الكبيرة باستخدام التمرير الافتراضي (أكثر من 100,000 عنصر)
📝 تمارين
-
أسئلة أساسية (مستوى الصعوبة: ⭐)
استخدم v-if/v-else للتبديل بين حالتي تسجيل الدخول وتسجيل الخروج:
- تم تسجيل الدخول: يعرض اسم المستخدم وزر «تسجيل الخروج»
- لم يتم تسجيل الدخول: يعرض عبارة «يرجى تسجيل الدخول» ونموذج تسجيل الدخول
- استخدم «موديلو» لتجميع عناصر متعددة
-
مسائل متقدمة (مستوى الصعوبة: ⭐⭐)
تنفيذ قائمة المهام باستخدام
v-for:- 3 حالات: الكل / قيد التنفيذ / مكتمل (يمكنك التبديل بينها باستخدام الأزرار الثلاثة)
- تعرض القائمة المهام مع الstatus المقابلة لها
- يحتوي كل عنصر على زرين هما «إكمال» و«حذف»
- تظهر العبارة «غير مكتمل: N عنصر» في الأسفل
-
مسألة التحدي (مستوى الصعوبة: ⭐⭐⭐)
تنفيذ نظام متكامل لتصفية المنتجات وفرزها وتقسيمها إلى صفحات (5 ملفات):
- بيانات المنتج: 30 سجلًا، تشمل الاسم والسعر والفئة وتاريخ الإنشاء
- البحث: إجراء بحث تقريبي حسب الاسم
- التصفية حسب الفئة: 5 فئات (الكل/الهاتف/الكمبيوتر المحمول/الجهاز اللوحي/سماعات الرأس)
- الترتيب حسب: السعر (تصاعدي/تنازلي)، الاسم، الأحدث
- ترقيم الصفحات: 10 عناصر في كل صفحة، مع التنقل باستخدام أرقام الصفحات
- الأداء: تخزين النتائج المحسوبة والمصفاة/المرتبة في ذاكرة التخزين المؤقت
- تحسين القوائم الكبيرة: 1,000 عنصر اختبار لضمان الأداء السلس