Vue.js: المكونات الديناميكية وغير المتزامنة

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

تتيح لك المكونات الديناميكية عرض مكونات مختلفة ديناميكيًا باستخدام tag <component> واحدة — حيث يمكنك التبديل بين علامات التبويب والنوافذ المنبثقة وطرق العرض بناءً على البيانات. أما المكونات غير المتزامنة فتتيح لك تحميل المكونات حسب الحاجة — حيث يتم تحميل الكود الضروري فقط في الشاشة الأولى لتحسين الأداء.

تم تصميم المكون التجريبي <Suspense> في Vue 3 خصيصًا للتعامل مع status تحميل المكونات غير المتزامنة، مما يلغي الحاجة إلى كتابة v-if="loading" يدويًّا. سيساعدك هذا الدرس على إتقان ثلاث واجهات برمجة تطبيقات أساسية.

1. ما ستتعلمه



2. كابوس وجود 5 عبارات v-if في tag تبويب واحدة

(1) المشكلة: 5 علامات تبويب، و10 توجيهات v-if متداخلة

قامت أليس بإنشاء لوحة تحكم إدارية مزودة بعلامات تبويب:

VUE
<!-- ❌ 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 علامات تبويب

VUE
<!-- ✅ 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) الإيرادات

بعد استخدام المكونات الديناميكية:



3. <component :is="..."> الأساسيات

(1) 5 طرق لاستخدام :is

VUE
<!-- 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) مثال كامل

VUE
<!-- 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. أما المكونات الكبيرة (مثل محررات النص المنسق ومكتبات الرسوم البيانية)، فهي ليست ضرورية للشاشة الأولى، لكنها ستعيق عملية التحميل. أما المكونات غير المتزامنة، فتتم تحميلها عند الحاجة، مما يؤدي إلى ظهور الشاشة الأولى بشكل أسرع.

VUE
<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) الترميز المبسط

VUE
<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" يدويًّا.

VUE
<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) فتحتان

VUE
<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) السلوك الافتراضي: يؤدي تبديل المكونات إلى إتلافها وإعادة إنشائها

VUE
<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) ذاكرة التخزين المؤقتة للحفاظ على الاتصال (لتجنب إنشاء نسخ مكررة)

VUE
<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»

VUE
<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. أساسيات علامات التبويب الديناميكية

VUE
<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

VUE
<!-- 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»

VUE
<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 التبديل المتكرر
غير متزامن + الحفاظ على الاتصال ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ أفضل الممارسات


❓ أسئلة شائعة

س ما الفرق بين المكونات الديناميكية وـ v-if؟
ج مع v-if، حتى لو كان هناك 5 شروط و5 فروع، فإن المكون يُحمَّل بالكامل. أما مع المكونات الديناميكية، فيتم استخدام tag <component> واحدة، ويقوم المكون بالتبديل بناءً على السمة :is، مما يجعله أكثر إيجازًا. كما يمكن تحميل المكونات غير المتزامنة عند الحاجة.
س هل يجب أن تكون defineAsyncComponent في المستوى الأعلى من setup؟
ج نعم. يجب استدعاء defineAsyncComponent(() => import('...')) في المستوى الأعلى من <script setup> (وليس داخل دالة).
س هل تُعد ميزة Suspense ميزة مستقرة؟
ج إنها مستقرة في الإصدار Vue 3.2 والإصدارات الأحدث. يمكنك استخدامها بثقة، لكننا نوصي باختبار سلوك الحل البديل بدقة في بيئات الإنتاج.
س هل يمكن للمكونات الديناميكية تمرير الخصائص (props)؟
ج نعم. استخدم v-bind مباشرةً: <component :is="Comp" :prop1="x" :prop2="y" />.
س كيف يمكنني التقاط الأخطاء في المكونات غير المتزامنة؟
ج استخدم الخيار errorComponent + onErrorCaptured: { loader, errorComponent: ErrorComp }. داخل Suspense، يمكنك تغليف الكود باستخدام onErrorCaptured.
س هل يتم الاحتفاظ ببيانات المكونات المخزنة مؤقتًا بواسطة keep-alive؟
ج يتم الاحتفاظ بالبيانات (ولا يتم حذفها) طالما أنها مخزنة مؤقتًا. ومع ذلك، فإن تحديث الصفحة أو التبديل بين المسارات سيؤدي إلى مسح ذاكرة التخزين المؤقت. استخدم Pinia لضمان استمرارية البيانات.
س ما هي العلاقة بين defineAsyncComponent والتحميل المؤجل في Vue Router؟
ج يستخدم Vue Router داخليًّا defineAsyncComponent لتنفيذ التحميل المؤجل للمسارات. وبشكل أساسي، هما نفس الشيء.

📖 ملخص


📝 تمارين

  1. أسئلة أساسية (مستوى الصعوبة: ⭐)

    تنفيذ أداة بسيطة للتبديل بين علامات التبويب:

    • 3 علامات تبويب (الصفحة الرئيسية/الملف الشخصي/الإعدادات)
    • استخدم <component :is> للتبديل ديناميكيًا
    • الإخراج على Switch: "تم إنشاء/إزالة المكون X"
  2. مسائل متقدمة (مستوى الصعوبة: ⭐⭐)

    تنفيذ tag التبويب + التحميل غير المتزامن:

    • 3 علامات تبويب؛ يتم تحميل tag التبويب «الإعدادات» بشكل غير متزامن باستخدام defineAsyncComponent
    • عرض status التحميل (رمز "v" مكتوب بخط اليد في status التحميل)
    • لا يتم إعادة التحميل عند العودة إلى tag التبويب «الصفحة الرئيسية»
  3. مسألة التحدي (مستوى الصعوبة: ⭐⭐⭐)

    تنفيذ نظام كامل ديناميكي وغير متزامن ومزود بخاصية التخزين المؤقت:

    1. 5 علامات تبويب، يتم تحميل اثنتين منها على الأقل بشكل غير متزامن
    2. لفها بـ <Suspense>
    3. <keep-alive :max="3"> ذاكرة التخزين المؤقت
    4. إخراج الإشارة onActivated/onDeactivated عند التبديل
    5. عرض مكون الخطأ (ErrorComponent) عند فشل مكون غير متزامن
    6. قياس وقت تحميل الصفحة الأولى (قبل/بعد التزامن)
    7. TypeScript: مكونات ديناميكية ذات أنواع محددة بدقة
Web-Tutorial.com

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

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

100%