Vue.js: المكونات الديناميكية وغير المتزامنة
آخر تحديث: 2026-08-26
تتيح لك المكونات الديناميكية عرض مكونات مختلفة ديناميكيًا باستخدام tag <component> واحدة — حيث يمكنك التبديل بين علامات التبويب والنوافذ المنبثقة وطرق العرض بناءً على البيانات. أما المكونات غير المتزامنة فتتيح لك تحميل المكونات حسب الحاجة — حيث يتم تحميل الكود الضروري فقط في الشاشة الأولى لتحسين الأداء.
تم تصميم المكون التجريبي <Suspense> في Vue 3 خصيصًا للتعامل مع status تحميل المكونات غير المتزامنة، مما يلغي الحاجة إلى كتابة v-if="loading" يدويًّا. سيساعدك هذا الدرس على إتقان ثلاث واجهات برمجة تطبيقات أساسية.
1. ما ستتعلمه
<component :is="...">أساسيات المكونات الديناميكية- طريقتان لكتابة
:is: سلسلة / مكون كائن defineAsyncComponentالتحميل غير المتزامن- مزايا تقسيم الكود (الاستيراد الديناميكي) من حيث الأداء
<Suspense>التعامل مع المكونات غير المتزامنة- المكونات الديناميكية + التخزين المؤقت مع الحفاظ على الاتصال
- 5 سيناريوهات واقعية و3 أنماط خاطئة
2. كابوس وجود 5 عبارات v-if في tag تبويب واحدة
(1) المشكلة: 5 علامات تبويب، و10 توجيهات v-if متداخلة
قامت أليس بإنشاء لوحة تحكم إدارية مزودة بعلامات تبويب:
<!-- ❌ The "Broken" Version: 5 Tabs with 5 v-if -->
<template>
<div class="tabs">
<button @click="currentTab = 'home'">Home</button>
<button @click="currentTab = 'profile'">Profile</button>
<button @click="currentTab = 'settings'">Settings</button>
<div v-if="currentTab === 'home'">
<HomeTab />
</div>
<div v-else-if="currentTab === 'profile'">
<ProfileTab />
</div>
<div v-else-if="currentTab === 'settings'">
<SettingsTab />
</div>
</div>
</template>
مدير المنتج تشارلي:
«أليس، نحتاج إلى 5 علامات تبويب إضافية. 10 أسطر من v-if-else لا يمكن صيانتها. استخدمي مكونًا ديناميكيًا.»
(2) حل المكونات الديناميكية في Vue: 1 <component> للتبديل بين 5 علامات تبويب
<!-- ✅ Correct Version: 1 <component> Switch -->
<template>
<div classe="tabs">
<button
v-for="tab in tabs"
:key="tab.name"
:classe="{ active: currentTab === tab.name }"
@click="currentTab = tab.name"
>
{{ tab.label }}
</button>
<!-- ✅ 1 <component> Dynamic Tag Switching -->
<component :is="currentTabComponent" />
</div>
</template>
<script setup>
import { ref, computed, defineAsyncComponent } from 'vue'
// Synchronization Components
import HomeTab from './tabs/HomeTab.vue'
import ProfileTab from './tabs/ProfileTab.vue'
// Asynchronous Components(Laden on Demand)
const SettingsTab = defineAsyncComponent(() => import('./tabs/SettingsTab.vue'))
const tabs = [
{ name: 'home', label: 'Home' },
{ name: 'profile', label: 'Profile' },
{ name: 'settings', label: 'Settings' }
]
const currentTab = ref('home')
// Computed Properties:According to currentTab Determine which component to display
const currentTabComponent = computed(() => {
return { home: HomeTab, profile: ProfileTab, settings: SettingsTab }[currentTab.value]
})
</script>
(3) الإيرادات
بعد استخدام المكونات الديناميكية:
- حجم الكود: 10 أسطر من v-if → سطر واحد من
<component>(-90%) - قابل للتوسع: لا تتطلب إضافة tag تبويب جديدة سوى عملية استيراد واحدة وكائن مكون واحد
- أداء الشاشة الأولى: يوفر التحميل غير المتزامن لtag التبويب «الإعدادات» حوالي 50 كيلوبايت
- سهولة الصيانة: 5 علامات تبويب تُدار ضمن array واحدة
3. <component :is="..."> الأساسيات
(1) 5 طرق لاستخدام :is
<!-- Writing Style 1:Component Name String(Registered globally) -->
<component :is="'HomeTab'" />
<!-- Writing Style 2:Component Object(Most Commonly Used) -->
<component :is="HomeTab" />
<!-- Writing Style 3:Dynamically Calculated Properties -->
<component :is="currentTabComponent" />
<!-- Writing Style 4:Asynchronous Component Object -->
<component :is="asyncComponent" />
<!-- Writing Style 5:Inline Component Object -->
<component :is="{ template: '<div>Inline</div>' }" />
(2) مثال كامل
<!-- TabContainer.vue -->
<template>
<div>
<button
v-for="tab in tabs"
:key="tab.name"
@click="currentTab = tab.name"
>
{{ tab.label }}
</button>
<!-- Dynamic Components -->
<component :is="currentTabComponent" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import HomeTab from './tabs/HomeTab.vue'
import ProfileTab from './tabs/ProfileTab.vue'
import SettingsTab from './tabs/SettingsTab.vue'
const tabs = [
{ name: 'home', label: 'Home' },
{ name: 'profile', label: 'Profile' },
{ name: 'settings', label: 'Settings' }
]
const currentTab = ref('home')
const currentTabComponent = computed(() => ({
home: HomeTab,
profile: ProfileTab,
settings: SettingsTab
}[currentTab.value]))
</script>
(3) 5 سيناريوهات رئيسية للتطبيق
| المشهد | المكون الديناميكي |
|---|---|
| التبديل بين علامات التبويب | ✅ |
| النص/الحوار | ✅ |
| النماذج متعددة الخطوات | ✅ |
| عرض المسار | ✅ (مكون داخلي <router-view>) |
| تبديل السمة (تصميمات مختلفة) | ✅ |
4. المكونات غير المتزامنة defineAsyncComponent
(1) لماذا نحتاج إلى المكونات غير المتزامنة؟
تقوم المكونات القياسية بتحميل جميع الأكواد على الفور عند import. أما المكونات الكبيرة (مثل محررات النص المنسق ومكتبات الرسوم البيانية)، فهي ليست ضرورية للشاشة الأولى، لكنها ستعيق عملية التحميل. أما المكونات غير المتزامنة، فتتم تحميلها عند الحاجة، مما يؤدي إلى ظهور الشاشة الأولى بشكل أسرع.
<script setup>
import { defineAsyncComponent } from 'vue'
import LoadingSpinner from './LoadingSpinner.vue'
// Asynchronous Components:Only when using ChartEditor Load its code only when needed
const ChartEditor = defineAsyncComponent({
// 1. Load Function
loader: () => import('./ChartEditor.vue'),
// 2. Display while loading
loadingComponent: LoadingSpinner,
// 3. Display when loading fails
errorComponent: ErrorMessage,
// 4. Load Delay(Prevent Flickering)
delay: 200
})
</script>
<template>
<ChartEditor v-if="showEditor" />
</template>
(2) الترميز المبسط
<script setup>
import { defineAsyncComponent } from 'vue'
// ✅ Simplified Version:Only loader
const ChartEditor = defineAsyncComponent(() => import('./ChartEditor.vue'))
</script>
(3) 5 سيناريوهات رئيسية للمكونات غير المتزامنة
| السيناريو | التحميل غير المتزامن |
|---|---|
| محرر النص المنسق | ✅ |
| مكتبة الرسوم البيانية (ECharts/D3) | ✅ |
| النماذج الكبيرة (التي تحتوي على 100 حقل أو أكثر) | ✅ |
| نافذة منبثقة من نوع «Modal» | ✅ |
| التحميل المؤجل للمسارات | ✅ (يتم استخدامه تلقائيًا بواسطة Vue Router) |
5. <Suspense> التعامل مع المكونات غير المتزامنة
(1) ما هو «السوسبانس»؟
يُعد مكون Suspense منسق تحميل المكونات غير المتزامن في Vue 3 — فهو يتولى تلقائيًا إدارة حالات التحميل والأخطاء للمكونات غير المتزامنة. ودّعوا كتابة v-if="loading" يدويًّا.
<template>
<!-- Suspense Automatically wait for asynchronous components to finish loading -->
<Suspense>
<!-- Asynchronous Components -->
<ChartEditor :data="chartData" />
<!-- Display while loading(Default Slot) -->
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</template>
(2) فتحتان
<template>
<Suspense>
<!-- Default Slot:Asynchronous Content -->
<AsyncComponent />
<!-- Fallback Slot: Display while loading -->
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
(3) 5 مزايا رئيسية
| الميزة | الوصف |
|---|---|
| البساطة | لا حاجة إلى كتابة كود يدوي لstatus التحميل |
| موحدة | المعالجة الموحدة لعدة مكونات غير متزامنة |
| التداخل | يدعم Suspense المتداخل |
| معالجة الأخطاء | تعمل مع onErrorCaptured |
| متوافق مع SSR | الانتظار التلقائي من جانب الخادم |
6. المكونات الديناميكية + التخزين المؤقت مع الحفاظ على الاتصال
(1) السلوك الافتراضي: يؤدي تبديل المكونات إلى إتلافها وإعادة إنشائها
<template>
<component :is="currentTabComponent" />
</template>
<!-- Switch Tab:
1. Legacy Components onBeforeUnmount
2. Legacy Components onUnmounted
3. New Component onBeforeMount
4. New Component onMounted
5. Data is reloaded every time you switch(Poor performance)
-->
(2) ذاكرة التخزين المؤقتة للحفاظ على الاتصال (لتجنب إنشاء نسخ مكررة)
<template>
<keep-alive>
<component :is="currentTabComponent" />
</keep-alive>
</template>
<!-- Switch Tab:
1. Legacy Components onDeactivated(Destroy without stopping)
2. New Component onActivated(Restore from Cache)
3. Data Retention,Scroll Position Retention
-->
(3) إعدادات المفتاح «keep-alive 3»
<template>
<!-- include:Cache only the specified components -->
<keep-alive include="HomeTab,ProfileTab">
<component :is="currentTabComponent" />
</keep-alive>
<!-- exclude:Do not cache the specified component -->
<keep-alive exclude="SettingsTab">
<component :is="currentTabComponent" />
</keep-alive>
<!-- max: Maximum 5 Cached -->
<keep-alive :max="5">
<component :is="currentTabComponent" />
</keep-alive>
</template>
7. مثال كامل: علامات التبويب الديناميكية + التحميل غير المتزامن
▶ مثال: 1. أساسيات علامات التبويب الديناميكية
<template>
<div>
<nav>
<button
v-for="tab in tabs"
:key="tab.name"
:class="{ active: currentTab === tab.name }"
@click="currentTab = tab.name"
>
{{ tab.label }}
</button>
</nav>
<component :is="currentTabComponent" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import HomeTab from './tabs/HomeTab.vue'
import ProfileTab from './tabs/ProfileTab.vue'
const tabs = [
{ name: 'home', label: 'Home' },
{ name: 'profile', label: 'Profile' },
{ name: 'settings', label: 'Settings' }
]
const currentTab = ref('home')
const currentTabComponent = computed(() => ({
home: HomeTab,
profile: ProfileTab,
settings: SettingsTab
}[currentTab.value]))
</script>
▶ مثال: 2. المكونات غير المتزامنة + Suspense
<!-- App.vue -->
<template>
<button @click="show = !show">Toggle</button>
<Suspense v-if="show">
<!-- Asynchronous Components(Do not load on the first screen,Load only when clicked) -->
<HeavyChart :data="data" />
<template #fallback>
<div>Loading chart...</div>
</template>
</Suspense>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue'
const show = ref(false)
// ✅ Asynchronous Components:News import,Laden on Demand
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'))
const data = ref([1, 2, 3, 4, 5])
</script>
▶ مثال: 3. المكونات الديناميكية + التخزين المؤقت مع ميزة «keep-alive»
<template>
<div>
<button v-for="tab in tabs" @click="currentTab = tab.name">
{{ tab.label }}
</button>
<!-- keep-alive Cache: Preserve data when switching -->
<keep-alive :max="3">
<component :is="currentTabComponent" />
</keep-alive>
</div>
</template>
▶ مثال: 4. مقارنة بين 5 طرق لاستخدام :is
| القواعد النحوية | مثال | الاستخدام |
|---|---|---|
| سلسلة | <component :is="'HomeTab'"> |
التسجيل العالمي (غير موصى به) |
| كائن مكون | <component :is="HomeTab"> |
الاستيراد الثابت (الأكثر شيوعًا) |
| كائن مكون غير متزامن | <component :is="AsyncComponent"> |
التحميل عند الطلب |
| الخاصية المحسوبة | <component :is="currentTabComponent"> |
التبديل الديناميكي |
| كائن مضمن | <component :is="{ template: '...' }"> |
سيناريو بسيط |
▶ مثال: 5. مرجع سريع لـ 5 أخطاء شائعة
| الخطأ | الأعراض | الحل |
|---|---|---|
| :سلسلة نصية ولكنها غير مسجلة عالميًا | تحذير: مكون غير معروف | استخدم كائن مكون أو قم بتسجيله أولاً |
| المكونات غير المتزامنة لا تحتوي على خيار بديل | شاشة فارغة أثناء التحميل | استخدام Suspense |
| تبديل المكونات لإعادة تحميل البيانات | ضعف الأداء | استخدام التخزين المؤقت مع ميزة «keep-alive» |
| الأخطاء غير المعالجة في المكونات غير المتزامنة | حالات الفشل الصامتة | الاستخدام مع onErrorCaptured |
| نسيت إضافة :key إلى المكون الديناميكي | عدم اتساق الstatus | إضافة :key="tab.name" |
▶ مثال: 6. 5 مقارنات رئيسية في الأداء
| الوضع | الشاشة الأولى | التبديل | قابل للتطبيق |
|---|---|---|---|
| الاستيراد المباشر | بطيء | إعادة التحميل | عنصر واجهة المستخدم |
| الاستيراد الديناميكي | سريع | بطيء عند التبديل الأول | المكونات الكبيرة |
| التشويق | السرعة | السلاسة | تجربة المستخدم أولاً |
| الحفاظ على الاتصال | سريع | إعادة التحميل 0 | التبديل المتكرر |
| غير متزامن + الحفاظ على الاتصال | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | أفضل الممارسات |
❓ أسئلة شائعة
<component> واحدة، ويقوم المكون بالتبديل بناءً على السمة :is، مما يجعله أكثر إيجازًا. كما يمكن تحميل المكونات غير المتزامنة عند الحاجة.defineAsyncComponent في المستوى الأعلى من setup؟defineAsyncComponent(() => import('...')) في المستوى الأعلى من <script setup> (وليس داخل دالة).v-bind مباشرةً: <component :is="Comp" :prop1="x" :prop2="y" />.errorComponent + onErrorCaptured: { loader, errorComponent: ErrorComp }. داخل Suspense، يمكنك تغليف الكود باستخدام onErrorCaptured.keep-alive؟defineAsyncComponent والتحميل المؤجل في Vue Router؟defineAsyncComponent لتنفيذ التحميل المؤجل للمسارات. وبشكل أساسي، هما نفس الشيء.📖 ملخص
- المكونات الديناميكية:
<component :is="...">التبديل بين مكونات متعددة باستخدام tag تبويب واحدة - 5 طرق لاستخدام
:is: السلسلة / كائن المكون / غير المتزامن / الخصائص المحسوبة / المضمنة - المكون غير المتزامن
defineAsyncComponent: يتم تحميله عند الحاجة لتسريع عرض الشاشة الأولى - Suspense: يتولى تلقائيًا إدارة status التحميل للمكونات غير المتزامنة
- Keep-Alive + المكونات الديناميكية: التخزين المؤقت لمنع إنشاء نسخ مكررة
- 5 سيناريوهات رئيسية: علامات التبويب / النوافذ المنبثقة / التوجيه / النماذج متعددة الخطوات / السمات
- أفضل الممارسات في مجال الأداء: العمل غير المتزامن + ميزة «Keep-Alive» + ميزة «Suspense»
📝 تمارين
-
أسئلة أساسية (مستوى الصعوبة: ⭐)
تنفيذ أداة بسيطة للتبديل بين علامات التبويب:
- 3 علامات تبويب (الصفحة الرئيسية/الملف الشخصي/الإعدادات)
- استخدم
<component :is>للتبديل ديناميكيًا - الإخراج على Switch: "تم إنشاء/إزالة المكون X"
-
مسائل متقدمة (مستوى الصعوبة: ⭐⭐)
تنفيذ tag التبويب + التحميل غير المتزامن:
- 3 علامات تبويب؛ يتم تحميل tag التبويب «الإعدادات» بشكل غير متزامن باستخدام
defineAsyncComponent - عرض status التحميل (رمز "v" مكتوب بخط اليد في status التحميل)
- لا يتم إعادة التحميل عند العودة إلى tag التبويب «الصفحة الرئيسية»
- 3 علامات تبويب؛ يتم تحميل tag التبويب «الإعدادات» بشكل غير متزامن باستخدام
-
مسألة التحدي (مستوى الصعوبة: ⭐⭐⭐)
تنفيذ نظام كامل ديناميكي وغير متزامن ومزود بخاصية التخزين المؤقت:
- 5 علامات تبويب، يتم تحميل اثنتين منها على الأقل بشكل غير متزامن
- لفها بـ
<Suspense> <keep-alive :max="3">ذاكرة التخزين المؤقت- إخراج الإشارة onActivated/onDeactivated عند التبديل
- عرض مكون الخطأ (ErrorComponent) عند فشل مكون غير متزامن
- قياس وقت تحميل الصفحة الأولى (قبل/بعد التزامن)
- TypeScript: مكونات ديناميكية ذات أنواع محددة بدقة