Skip to content

أساسيات المكونات

المكونات تسمح لنا بتقسيم واجهة المستخدم إلى قطع مستقلة وقابلة لإعادة الاستخدام، و تصورها على شكل قطع كل واحدة معزولة على حدة. من الشائع أن يُنظم تطبيق معين على شكل شجرة من المكونات المتداخلة :

Component Tree

هذا مشابه جدا لكيفية تداخل عناصر HTML الأصلية، ولكن Vue تنفذ نموذج مكوناته الخاص به للسماح لنا بتغليف محتوى مخصص وشيفرة في كل مكون. Vue تعمل أيضا بشكل جيد مع الأصلية. إذا كنت مهتمًا بالعلاقة بين مكونات Vue ومكونات الـWeb الأصلية، اطلع على المزيد من هنا.

تعريف مكون

عند استخدام عملية بناء، نحدد عادة كل مكون Vue في ملف منفصل باستخدام امتداد .vue - معروف باسم مكون أحادي الملف (SFC اختصارا):

vue
<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">لقد نقرت عليّ {{ count }} مرّة.</button>
</template>
vue
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">لقد نقرت عليّ {{ count }} مرّة.</button>
</template>

عند عدم استخدام عملية بناء، يمكن تعريف مكون Vue ككائن JavaScript عادي يحتوي على خيارات Vue المحددة :

js
export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      لقد نقرت عليّ {{ count }} مرّة.
    </button>`
}
js
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
       لقد نقرت عليّ {{ count }} مرّة.
    </button>`
  // يمكنها أيضا استهداف قالب موجود في DOM
  // template: '#my-template-element'
}

هنا القالب مضمن كسلسلة نصية تحتوي على شيفرة JavaScript ، وستُصرَّف من قبل Vue على الفور. يمكنك أيضًا استخدام محدد ID يشير إلى عنصر (عادة عناصر الـ<template> الأصلية) - ستستخدم Vue محتواه كمصدر للقالب.

المثال أعلاه يعرف مكونًا واحدًا ويصدره كتصدير افتراضي (export default) في ملف ذو امتداد js.، لكن يمكنك استخدام التصديرات المسماة (export componentName) لتصدير عدة مكونات من نفس الملف.

استخدام مكون

ملاحظة

سنستخدم صيغة الـSFC فيما تبقى من هذا الدليل - المفاهيم حول المكونات هي نفسها بغض النظر عن ما إذا كنت تستخدم عملية بناء أم لا. يوضح قسم الأمثلة استخدام المكونات في كلا الحالتين.

من أجل استخدام مكون ابن، نحتاج إلى استيراده في المكون الأب. بفرض أننا وضعنا مكون "العداد" داخل ملف يسمى ButtonCounter.vue، سيُعرض المكون كتصدير افتراضي داخل الملف :

vue
<script>
import ButtonCounter from './ButtonCounter.vue'

export default {
  components: {
    ButtonCounter
  }
}
</script>

<template>
  <h1>هنا المكون الابن</h1>
  <ButtonCounter />
</template>

لكي تعرض المكون المستورد إلى القالب، تحتاج إلى تسجيله باستخدام خيار components. سيكون المكون متاحًا كوسم باستخدام اسم الخاصية التي سُجل بها.

vue
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>هنا المكون الابن</h1>
  <ButtonCounter />
</template>

باستخدام <script setup> ، يتم توفير المكونات المستوردة تلقائيًا في القالب.

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

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

template
<h1>هنا يوجد العدد من المكونات الابن</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

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

في المكونات أحادية الملف SFC ، ينصح باستخدام أسماء الوسوم بنمط باسكال PascalCase للمكونات الابن لتمييزها من عناصر HTML الأصلية. على الرغم من أن أسماء وسوم HTML الأصلية لا غير حساسة لحالة الأحرف، إلا أن Vue SFC هو صيغة مُصرَّفة لذلك يمكننا استخدام أسماء وسوم حساسة لحالة الأحرف. يمكننا أيضًا استخدام /> لإغلاق الوسم.

إذا كنت تكتب القوالب مباشرة في DOM (على سبيل المثال ، كمحتوى في عنصر <template> أصلي) ، فسيكون القالب خاضعا لسلوك المتصفح في تحليل الـHTML الأصلي . في هذه الحالات ، ستحتاج إلى استخدام نمط أسياخ الشواء kebab-case وعلامات إغلاق صريحة لوسوم المكونات :

template
<!-- إذا كان هذا القالب مكتوبا في 
DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

اطلع على تنبيهات حول تحليل قالب DOM لمزيد من التفاصيل.

تمريرالخاصيات

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

الخاصيات هي سمات مخصصة يمكنك تسجيلها في مكون. لتمرير عنوان إلى مكون "مقال المدونة" ، يجب علينا تعريفه في قائمة الخاصيات التي يقبلها هذا المكون، باستخدام props التعليمة العامة defineProps:

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['title']
}
</script>

<template>
  <h4>{{ title }}</h4>
</template>

عندما يتم تمرير قيمة للخاصية ، تصبح هذه الخاصية متوفرة على مستوى نسخة مكون . يمكن الوصول إلى قيمة هذه الخاصية في القالب وفي سياق this التابع للمكون ، مثل أي خاصية أخرى للمكون.

vue
<!-- BlogPost.vue -->
<script setup>
 defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>

defineProps هي تعليمة عامة تنفذ خلالزمن التصريف و التي تتوفر فقط داخل <script setup> ولا تحتاج إلى استيرادها بشكل صريح. الخاصيات المعرفة داخله تعرض بشكل تلقائي للقالب. defineProps تعيد أيضًا كائنًا يحتوي على جميع الخاصيات الممررة إلى المكون، حتى نتمكن من الوصول إليها في شيفرة الـJavaScript إذا لزم الأمر :

js
const props = defineProps(['title'])
console.log(props.title)

اطلع على : إضافة الأنواع لخاصيات المكون

إذا كنت لا تستخدم <script setup> ، يجب عليك تعريف الخاصيات باستخدام خيار props ،وسيتم تمرير كائن الخاصيات إلى ()setup كأول وسيط:

js
export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

المكون يمكن أن يحتوي على عدد غير محدود من الخاصيات، وبشكل افتراضي، يمكن تمرير أي قيمة إلى أي خاصية.

بمجرد تسجيل الخاصية، يمكنك تمرير البيانات إليها كسمة مخصصة، كما يلي :

template
<BlogPost title="رحلتي مع Vue.js" />
<BlogPost title="التدوين بـVue.js" />
<BlogPost title="لماذا العمل بـVue يعتبر ممتعا" />

في تطبيق عادي، من المحتمل أن يكون لديك مصفوفة من المقالات في المكون الأب:

js
export default {
  // ...
  data() {
    return {
      posts: [
        { id: 1, title: "رحلتي مع Vue.js" },
        { id: 2, title: "التدوين بـVue.js" },
        { id: 3, title: "لماذا العمل بـVue يعتبر ممتعا" }
      ]
    }
  }
}
js
const posts = ref([
  { id: 1, title: "رحلتي مع Vue.js" },
  { id: 2, title: "التدوين بـVue.js" },
  { id: 3, title: "لماذا العمل بـVue يعتبر ممتعا" }
])

ثم نريد عرض مكون لكل مقالة باستخدام v-for:

template
<BlogPost
  v-for="post in posts"
  :key="post.id"
  :title="post.title"
 />

تجدر الإشارة إلى أن v-bind يستخدم لتمرير قيم الخاصية الديناميكية. هذا مفيد بشكل خاص عندما لا تعرف المحتوى الذي ستقوم بتصييره بدقة مسبقًا.

هذا كل ما تحتاج إلى معرفته حول الخاصيات إلى حد الآن، ولكن بمجرد انتهاءك من قراءة هذه الصفحة وفهم محتواها، ننصحك بالعودة لاحقًا لقراءة الدليل الكامل على الخاصيات.

الاستماع للأحداث

بينما نطور مكون <BlogPost>، قد تتطلب بعض الميزات التواصل مع المكون الأب. على سبيل المثال، قد نقرر تضمين ميزة سهولة الوصول (accessibility) لتكبير حجم النص في مقالات المدونة، بينما يُترك باقي الصفحة بحجمها الافتراضي.

في المكون الأب، يمكننا دعم هذه الميزة من خلال إضافة خاصية البيانات dataالمرجع التفاعلي ref باسمpostFontSize:

js
data() {
  return {
    posts: [
      /* ... */
    ],
    postFontSize: 1
  }
}
js
const posts = ref([
  /* ... */
])

const postFontSize = ref(1)

ويمكن استخدامه في القالب للتحكم في حجم الخط لجميع مقالات المدونة :

template
<div :style="{ fontSize: postFontSize + 'em' }">
  <BlogPost
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
   />
</div>

الآن، دعنا نضيف زر إلى قالب مكون <BlogPost>:

vue
<!-- BlogPost.vue, بدون <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>تكبير النص</button>
  </div>
</template>

الزر لا يقوم بأي شيئ حتى الآن - نريد أن يوصل النقر على الزر معلومة للمكون الأب لكي يقوم بتكبير حجم النص في جميع المقالات. لحل هذه المشكلة، توفر المكونات نظامًا مخصصًا للأحداث. يمكن للأب اختيار الاستماع إلى أي حدث من نسخة المكون الابن باستخدام v-on أو @، كما هو معتاد مع حدث الـDOM الأصلي :

template
<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

ثم يمكن للمكون الابن إرسال حدث على نفسه باستدعاء للتابع المدمج emit$، وتمرير اسم الحدث

vue
<!-- BlogPost.vue, بدون <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">تكبير النص</button>
  </div>
</template>

بفضل "enlarge-text="postFontSize += 0.1@، سيتم استقبال الحدث من قبل المكون الأب وتحديث قيمة postFontSize.

يمكننا اختيارياً التصريح بالأحداث المرسلة باستخدام خيار emits التعليمة العامة defineEmits :

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['title'],
  emits: ['enlarge-text']
}
</script>
vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

هذه الشيفرة توثق جميع الأحداث التي يرسلها مكون ما و بشكل اختياري تتحقق من صحتها . كما تسمح لـ Vue بتجنب تطبيقها تلقائياً كمستمعات أصلية على العنصر الجذري للمكون الابن.

مثل defineProps، التعليمة العامة defineEmits يمكن استخدامها فقط في <script setup> ولا تحتاج إلى استيراد. يعيد emit دالة تعادل الدالة emit$. يمكن استخدامها لإرسال الأحداث في قسم <script setup> من المكون ، حيث لا يمكن الوصول إلى emit$ مباشرة :

vue
<script setup>
const emit = defineEmits(['enlarge-text'])

emit('enlarge-text')
</script>

اطلع أيضا على: إضافة الأنواع للأحداث المرسلة Emits

إذا كنت لا تستخدم <script setup>، يمكنك تعريف الأحداث المرسلة باستخدام خيار emits. يمكنك الوصول إلى دالة emit كخاصية في سياق الخطافة setup (ممررة إلى ()setup كوسيط ثاني) :

js
export default {
  emits: ['enlarge-text'],
  setup(props, ctx) {
    ctx.emit('enlarge-text')
  }
}

هذا كل ما تحتاج معرفته حول الأحداث المخصصة للمكونات لحد الآن، لكن بمجرد انتهائك من قراءة هذه الصفحة وفهم محتواها ، نوصيك بالعودة لاحقًا لقراءة الدليل الكامل حول الأحداث المخصصة.

توزيع المحتوى باستخدام المنافذ

تمامًا كما هو الحال مع عناصر HTML، غالبًا ما يكون من المفيد أن تستطيع تمرير محتوى معين داخل أحد المكونات ، كما يلي :

template
<AlertBox>
  حدث خطأ ما.
</AlertBox>

وقد يتم تصييرها إلى شيء مثل:

هذا خطأ بهدف العرض

حدث خطأ ما.

يمكن بلوغ ذلك باستخدام العنصر المخصص <slot> الخاص بـVue:

vue
<template>
  <div class="alert-box">
    <strong>هذا خطأ بهدف العرض</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>

كما رأيت أعلاه، نستخدم <slot> كعنصر نائب حيث نريد أن يوضع المحتوى الممرر من المكون الأب - وهذا كل شيء. لقد انتهينا!

هذا كل ما تحتاج إلى معرفته حول المنافذ إلى حد الآن ، ولكن بمجرد انتهائك من قراءة هذه الصفحة وفهم محتواها ، نوصيك بالعودة لاحقًا لقراءة الدليل الكامل حول المنافذ.

المكونات الديناميكية

في بعض الأحيان ، يكون من المفيد التبديل بين المكونات بشكل ديناميكي، مثلا في واجهة علامات التبويب:

يتم تحقيق الأمر أعلاه بواسطة عنصر <component> الخاص بـ Vue باستخدام السمة is الخاصة:

template
<!-- يتغير المكون عند تغيير currentTab -->
<component :is="currentTab"></component>
template
<!-- يتغير المكون عند تغيير currentTab -->
<component :is="tabs[currentTab]"></component>

في المثال أعلاه، يمكن أن تحتوي القيمة الممررة إلى :is على أي من:

  • اسم المكون المسجل كسلسلة نصية، أو
  • كائن المكون المستورد حاليا

يمكنك أيضًا استخدام سمة is لإنشاء عناصر HTML عادية.

عند التبديل بين عدة مكونات مع <"..."=component :is>، سيتم فصل المكون الذي تم التبديل عنه. يمكننا إجبار المكونات غير النشطة على البقاء "نشطة" باستخدام المكون المدمج <KeepAlive>.

تنبيهات حول تحليل قالب DOM

إذا كنت تكتب قوالب Vue الخاصة بك مباشرة في DOM، فسيضطر Vue إلى استعادة نص قالب من DOM مباشرة. و قد يؤدي ذلك إلى بعض التحذيرات بسبب سلوك تحليل الـHTML الأصلي من طرف المتصفحات.

ملاحظة

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

  • مكونات أحادي الملف
  • قالب سلاسل نصية مضمنة (على سبيل المثال '...' :template)
  • <"script type="text/x-template>

عدم الحساسية لحالة الأحرف

تكون الوسوم وأسماء السمات في HTML غير حساسة لحالة الأحرف، لذا ستقوم المتصفحات بتحويل أي حروف كبيرة إلى أحرف صغيرة. وهذا يعني أنه عند استخدام قوالب DOM، أسماء المكونات بنمط باسكال (PascalCase) وأسماء الخاصيات بنمط سنام الجمل (camelCase) أو أسماء الأحداث v-on يجب أن يكتبوا بنمط أسياخ الشواء (kebab-case) :

js
//نمط سنام الجمل في //JavaScript
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
template
<!-- نظام أسياخ في HTML -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

الوسوم ذاتية الغلق

في أمثلة الشيفرات السابقة كنا نستخدم الوسوم ذاتية الغلق للمكونات :

template
<MyComponent />

لأن محلل قوالب Vue يعتبر </ كإشارة لإنهاء أي وسم، بغض النظر عن نوعها.

بالمقابل، في قوالب الـDOM، يجب علينا دائمًا تضمين وسوم الغلق بشكل صريح:

template
<my-component></my-component>

هذا لأن مواصفات HTML تسمح فقط لعدد قليل من العناصر المحددة بحذف وسوم الغلق، وأكثرها شيوعًا هي عناصر <input> و <img>. بالنسبة لجميع العناصر الأخرى، إذا حذفت وسم الغلق، فسيفترض المحلل الأصلي لـ HTML أنك لم تنهي وسم الفتح. على سبيل المثال، الشيفرة التالية:

template
<my-component /> <!-- نعتزم إغلاق الوسم هنا... -->
<span>مرحبا</span>

سُتحلل كالتالي:

template
<my-component>
  <span>مرحبا</span>
</my-component> <!-- ولكن المتصفح سيغلقها هنا. -->

قيود وضع العنصر

بعض عناصر HTML مثل <ul> و <ol> و <table> و <select> لها قيود على ما يمكن أن يُعرض داخلها، وبعض العناصر مثل <li> و <tr> و <option> يمكن أن تظهر فقط داخل عناصر محددة.

هذا سيؤدي إلى مشاكل عند استخدام المكونات مع العناصر التي لها هذه القيود. على سبيل المثال:

template
<table>
  <blog-post-row></blog-post-row>
</table>

المكون المخصص <blog-post-row> سيُرفع كمحتوى غير صالح، مما يسبب أخطاء في الناتج النهائي المُصيّر. يمكننا استخدام السمة الخاصة is كحل للمشكلة:

template
<table>
  <tr is="vue:blog-post-row"></tr>
</table>

ملاحظة

عند استخدام العناصر الأصلية لـ HTML، يجب أن تكون قيمة is مسبوقة بـ :vue لتُفسّر كمكون Vue. هذا مطلوب لتجنب الالتباس مع عناصر HTML المدمجة المُخصَّصة.

هذا كل ما تحتاج إلى معرفته حول الأخطاء في تحليل قوالب DOM إلى حد الآن - و بالفعل قد وصلنا إلى آخر محور في أساسيات Vue. مبارك عليك! لا يزال هناك المزيد لتعلمه، لكن أولاً، نوصي بالاستراحة للعب مع Vue بنفسك عن طريق إنشاء شيء ممتع، أو تحقق من بعض الأمثلة إذا لم تقم بذلك بعد.

بمجرد أن تفهم ما تم تناوله حتى الآن، انتقل إلى الدليل لتتعلم المزيد عن المكونات بشكل عميق.

أساسيات المكونات has loaded