Skip to content

المنافذ

لقراءة هذه الصفحة يجب عليك أولا الاطلاع على أساسيات المُكونات.ثم العودة إلى هنا.

محتوي المنفذ

تعلمنا سابقاً ان المُكونات تستقبل خاصيات (Props) , التي يمكن ان تكون أي نوعً من الأنواع الموجودة في لغة الچافاسكريبت . ولكن كيف يكون الأمر عندما نريد تمرير جزء من محتوي القالب (Template Content) ؟ إلي المُكون الإبن وجعل المُكون الإبن هو المسؤول عن عرض ذلك الجزء ضمن القالب (Template) الخاص به

علي سبيل المثال , لدينا مُكون <FancyButton> يعمل بهذا الشكل :

template
<FancyButton>
   <!-- هذا هو المحتوي الذي نريد تمريره --> ! أنقر الزر 
</FancyButton>

يكون القالب الخاص بالمُكون <FancyButton> بهذا الشكل :

template
<button class="fancy-btn">
    <slot></slot>  <!-- المنفذ من هذا القالب  FancyButton الذي يشير إلي المُحتوي القادم من المُكون -->
</button>

يُمثل العنصر <slot> المنفذ الذي يشير إلي المُحتوي القادم من المُكون الأب

slot diagram

الشكل النهائي المعروض في DOM :

html
<button class="fancy-btn"> ! أنقر الزر </button>

بإستخدام المنافذ , يكون المُكون <FancyButton> هو المسؤول عن عرض الوسم <button> و التنسيق الخاص به , بينما مُحتوي الزر يُعرض من خلال المُكون الأب <FancyButton>

هناك طريقة أخري لفهم مصطلح المنافذ و هو مقارنتها بالدوال (Functions) في لغة الجافاسكريبت

js
// مُكون يقوم بتمرير محتوي المنفذ 
FancyButton('! انقر الزر')

// دالة تعرض محتوي تلك المنفذ في قالب خاص بها
function FancyButton(slotContent) {
  return `<button class="fancy-btn">
      ${slotContent}
    </button>`
}

محتوي المنافذ يُمكن ان يكون اي نوع , هو ليس مقتصراً علي الهيئة النصية . ممكن ان يكون أكثر من عنصر أو يكون بداخله مُكون اخر

template
<FancyButton>
  <span style="color:red">! انقر الزر </span>
  <AwesomeIcon name="plus" />
</FancyButton>

إستخدام المنافذ سوف يجعل المُكون <FancyButton> أكثر مرونة و قابل لإعادة الإستخدام . يمكن الان إستخدامه في اماكن اخري مع تغير مُحتوي المنفذ و الأحتفاظ بنفس التنسيق و الخصائص

آلية عمل المنافذ الموجودة في المُكونات مستوحاة من فكرة <slot> المنافذ الموجودة في مُكون الويب الأصلي , ولكن تم إضافة إليه العديد من المُميزات التي سنتعرف عليها أسفل

نطاق العرض

يمكن تمرير إلي محتوي المنفذ جميع البيانات الموجودة بداخل النطاق الأب له لإنه مُعرف بداخل هذا النطاق مثال:

template
<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>

سيتم عرض نفس النتيجة للمتغير {{ message }} في كلتا الحالتين.

علي عكس السابق , محتوي المنفذ غير قادر علي الوصل إلي البيانات الموجودة داخل المُكون الإبن . طبقاً لقواعد النطاقات في لغة الجافاسكريبت (JavaScript's lexical scoping) يمكن الوصول إلي البيانات الموجودة في النطاق الذي تم تعريفها بداخله فقط , نفس القاعدة تُطبق علي التعبيرات البرمجية في قوالب فيو (Expressions in Vue Templates) . بعبارةً أخري :

التعبيرات البرمجية الموجودة في قالب الأب لديها حق الوصول فقط لنطاق الأب ; و التعبيرات البرمجية الموجودة في قالب الإبن لديها حق الوصول فقط لنطاق الإبن

المحتوى الإحتياطي

هناك بعض الحالات تريد عرض محتوي إحتياطي (default) للمنفذ في حالة عدم تقديم محتوي له . علي سبيل المثال , في المُكون <SubmitButton>:

template
<button type="submit">
  <slot></slot>
</button>

في حالة عدم تقديم محتوي لهذا المنفذ من خلال المُكون الأب , نريد عرض كلمة "تسليم" داخل الوسم <button> . لكي نجعل الكلمة "تسليم" هي المحتوي البديل لهذا المنفذ . يجب ان تُوضع هذه الكلمة بين وسم <slot>:

template
<button type="submit">
  <slot>     
    تسليم <!-- المُحتوي البديل -->
  </slot>
</button>

في حالة إستخدامنا للمُكون <SubmitButton> وعدم تقديم أي محتوي للمنفذ الخاص به

template
<SubmitButton />

سوف يتم عرض المحتوي الإحتياطي "تسليم"

html
<button type="submit">تسليم</button>

ولكن في حالة تقديم محتوي لهذا المنفذ

template
<SubmitButton>حفظ</SubmitButton>

سيتم عرض محتوي هذا المنفذ في الشكل النهائي:

html
<button type="submit">حفط</button>

المنافذ المسماة

هناك حالات عديدة نريد إستخدام أكثر من منفذ في مُكون واحد . مثل هذا القالب الخاص بالمُكون <BaseLayout> علي سبيل المثال:

template
<div class="container">
  <header>
    <!-- نريد تمرير محتوي الجزء الأعلي للمُكون هنا -->
  </header>
  <main>
    <!-- نريد تمرير المحتوي الأساسي للمُكون هنا -->
  </main>
  <footer>
    <!-- نريد تمرير محتوي الجزء السفلي للمُكون هنا -->
  </footer>
</div>

لكي نقوم بتنفيذ الشكل السابق نحتاج إلي تمرير أكثر من منفذ لهذا المُكون , و هنا يحتوي العنصر <slot> علي رمزاً يسمي name والذي دائماً ما يكون اسماً مميزاً لكل منفذ لتحديد أين سوف يتم عرضه في المُكون

template
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

في حالة عدم تحديد الإسم name للعنصر <slot> فإنه يأخذ الإسم "default" إفتراضياً

في المُكون الأب <BaseLayout> , نحتاج إلي تمرير أكثر من محتوي ,كل محتوي خاص بمنفذ محدد , و من هنا يأتي دول المنافذ المسماة

لكي نقوم بتمرير المنافذ المسماة , سوف نقوم بإستخدام العنصر <template> مع السمة المُوجهة (Directive) v-slot ومن ثم تمرير الإسم لهذه السمة كأنه وسيطاً (Argument):

template
<BaseLayout>
  <template v-slot:header>
    <!-- محتوي منفذ الجزء الأعلي  -->
  </template>
</BaseLayout>

يمكن كتابة <template v-slot:header> بطريقة مختصرة عن طريق دمج السمة v-slot مع الرمز #, لكي تُصبح <template #header> . يمكننا ان نتخيل الأمر علي أنه "عرض قالب في منفذ الجزء الأعلي للمُكون الإبن" .

named slots diagram

هذا هو الكود النهائي بعد ان قمنا بتمرير مُحتوي الثلاثة منافذ للمُكون <BaseLayout> بإستخدام الطريقة المُختصرة:

template
<BaseLayout>
  <template #header>
    <h1>هنا سوف يكون عنوان الصفحة</h1>
  </template>

  <template #default>
    <p>النص التوضيحي للمحتوي الأساسي للصفحة</p>
    <p>نص أخر.</p>
  </template>

  <template #footer>
    <p>هنا معلومات التواصل</p>
  </template>
</BaseLayout>

عندما يوجد في المُكون منافذ مسماة و أيضاً منفذ إفتراضي , يتم أعتبار جميع الوسوم ذات المستوي الأعلي التي لا تنتمي إلي ال <template> كمحتوي للمنفذ الإفتراضي . لذلك يُمكن الكتابة بهذا الشكل أيضاً.

template
<BaseLayout>
  <template #header>
    <h1>هنا سوف يكون عنوان الصفحة</h1>
  </template>

  <!-- implicit default slot -->
  <p>النص التوضيحي للمحتوي الأساسي للصفحة.</p>
  <p>نص أخر.</p>

  <template #footer>
    <p>هنا معلومات التواصل</p>
  </template>
</BaseLayout>

كل العناصر التي بداخل وسوم <template> تم تمريرها عن طريق المنافذ .الأن يكون شكل ال HTML النهائي:

html
<div class="container">
  <header>
    <h1>هنا سوف يكون عنوان الصفحة</h1>
  </header>
  <main>
    <p>النص التوضيحي للمحتوي الأساسي للصفحة</p>
    <p>نص أخر</p>
  </main>
  <footer>
    <p>هنا معلومات التواصل</p>
  </footer>
</div>

للتأكيد , يمكننا فهم المنافذ المسماة بشكل أعمق عن طريق تشبيهها بالدوال في لغة الجافاسكريبت:

js
// تمرير أكثر من منفذ بأسماء مختلفة
BaseLayout({
  header: `...`,
  default: `...`,
  footer: `...`
})

// <BaseLayout> يعرض مُحتوي المنافذ في وسوم مختلفة
function BaseLayout(slots) {
  return `<div class="container">
      <header>${slots.header}</header>
      <main>${slots.default}</main>
      <footer>${slots.footer}</footer>
    </div>`
}

الأسماء الديناميكية للمنافذ

يمكن لأسماء المنافذ ان تكون بشكل ديناميكي عن طريق إستخدام صيغة القالب مع v-slot:

template
<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>

  <!-- بإستخدام الطريقة المُختصرة -->
  <template #[dynamicSlotName]>
    ...
  </template>
</base-layout>

لاحظ ان التعبير البرمجي هو موضوع في قيود بناء الجمل البرمجية في الوسائط الديناميكية.

المنافذ محددة النطاق

كما ذكرنا في نطاق العرض, ان محتوي المنافذ لا يستطيع الوصول إلي البيانات الموجودة في المُكون الإبن.

لكن, هناك بعض الحالات التي نريد ان محتوي المنفذ يكون قادراً علي الوصول إلي البيانات من كلتا الإتجاهين من نطاق الأب و نطاق الإبن . لكي نحقق ذلك . نريد طريقة تُمكن المُكون الإبن من تمرير بياناته إلي المنفذ عند عرضها.

في الحقيقة , يمكننا ان نفعل ذلك - يمكننا ان نمرر الخواص من المُكون الإبن إلي المنفذ مثل تمرير الخاصيات (Props) إلي المُكون:

template
<!-- <MyComponent> قالب -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>

يُعد إستقبال الخاصيات من المنفذ (Slot Props) في حالة المنفذ الإفتراضي هو مختلف نوعاً ما عن في حالة المنافذ المسماة . سوف نعرض أولاً كيفية إستقبال الخاصيات في المنفذ الإفتراضي , بإستخدام v-slot مباشراً في المُكون الإبن :

template
<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

مُخطط المنافذ مُحددة النطاق

الخاصيات التي تم تمريرها إلي المنفذ من المُكون الإبن تكون موجودة كقيمة بداخل السمة v-slot , يمكن الوصول إليها كما في المُخطط السابق

يمكنك تصور ان المنافذ محددة النطاق عبارة عن دالة يتم تمريرها إلي المُكون الإبن . ثم يقوم المُكون الإبن باستدعائها ثم بتمرير الخاصيات كوسائط:

js
MyComponent({
  // تمرير المنفذ الإفتراضي كدالة
  default: (slotProps) => {
    return `${slotProps.text} ${slotProps.count}`
  }
})

function MyComponent(slots) {
  const greetingMessage = 'مرحباً'
  return `<div>${
    // إستدعاء دالة المنفذ و إعطاء قيمة الخاصيات لها
    slots.default({ text: greetingMessage, count: 1 })
  }</div>`
}

في الحقيقة . هذه الطريقة هي قريبة جداً من طريقة إنتاج المنافذ مُحددة النطاق . وكيف ستستخدم المنافذ المحددة النطاق في وظائف العرض.

لاحظ كيف التشابه بين إستخدام الدوال كالمثال السابق و v-slot="slotProps" . كما في وسائط الدالة (function arguments) , يمكننا تفككيك الكائن v-slot .

template
<MyComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</MyComponent>

المنفذ محددة النطاق المسماة

تعمل المنافذ محددة النطاق بشكل مشابه - حيث يمكن الوصول إلي خاصيات المنافذ من السمة v-slot . بشكل v-slot:name="slotProps" . عند إستخدام الطريقة المُخصرة تكون بالشكل التالي :

template
<MyComponent>
  <template #header="headerProps">
    {{ headerProps }}
  </template>

  <template #default="defaultProps">
    {{ defaultProps }}
  </template>

  <template #footer="footerProps">
    {{ footerProps }}
  </template>
</MyComponent>

تمرير الخاصيات إلي المنافذ المسماة:

template
<slot name="header" message="hello"></slot>

لاحظ أن الخاصية name لن تكون موجودة ضمن خاصيات المنفذ لأنها كلمة محجوزة - لذلك ستكون نتيجة headerProps هي { message: 'hello' }.

إذا كنت تريد الدمج بين المنافذ المسماة و منفذ النطاق الإفتراضي يجب إستخدام الوسم <template> للمنفذ الإفتراضي . إذا تم إستخدام v-slot مباشرةً مع وسم المُكون يُعطي خطأ . شاهد هذا المثال التوضيحي علي هذه الفقرة

template
<!-- هذا القالب لن يعمل -->
<template>
  <MyComponent v-slot="{ message }">
    <p>{{ message }}</p>
    <template #footer>
      <!-- message: الخاص بالمنفذ الإفتراضي الغير متاح في هذه الحالة-->
      <p>{{ message }}</p>
    </template>
  </MyComponent>
</template>

إستخدام الوسم <template> مع المنفذ الإفتراضي يجعل الأمر واضحاً أكثر لان الخاصية message لا يمكن إستخدامها داخل المنافذ الأخري :

template
<template>
  <MyComponent>
    <!-- يستخدم مع المنفذ الإفتراضي -->
    <template #default="{ message }">
      <p>{{ message }}</p>
    </template>

    <template #footer>
      <p>معلومات التواصل</p>
    </template>
  </MyComponent>
</template>

مثال Fancy List

قد تتساءل الان عن الإستخدامات الواقعية للمنافذ مُحددة النطاق . تخيل اننا لدينا مُكون <FancyList> يقوم بعرض قائمة من العناصر - يقوم بتجميع البيانات بشكل غير متزامن , استخدام تلك البيانات لعرض قائمة العناصر , او تطبيق أشياء مُتقدمة مثل ترقيم الصفحات (pagination) او التمرير اللانهائي (infinite scrolling). و نريد هذا المُكون مرن مع عرض كل العناصر و ندع المُكون الأب هو المسوؤل عن وضع الشكل الخاص بكل عنصر . لذلك يكون الإستخدام بهذا الشكل :

template
<FancyList :api-url="url" :per-page="10">
  <template #item="{ body, username, likes }">
    <div class="item">
      <p>{{ body }}</p>
      <p>by {{ username }} | {{ likes }} likes</p>
    </div>
  </template>
</FancyList>

داخل المُكون <FancyList> , سوف نعرض لكل عنصر من قائمة العناصر المنفذ الخاص به .(لاحظ اننا نستخدم v-bind لتمرير الكائن كخاصية للمنفذ)

template
<ul>
  <li v-for="item in items">
    <slot name="item" v-bind="item"></slot>
  </li>
</ul>

المُكونات عديمة المُحتوي

كما ذكرنا من قبل أن المُكون <FancyList> يقوم بتجميع المميزات الخاصة به (جلب البيانات , ترقيم الصفحات الخ) و عرض قائمة العناصر عن طريق المنفذ محدد النطاق .

إذا قمنا بتعديل شئ بسيط علي هذا المفهوم , ان المُكون مازال يقوم بتجميع المميزات الخاصة به من جلب بيانات و ترقيم الصفحات الخ ولكن لا يقوم بعرض اي شئ - و ان المحتوي الخاص به يكون من المُكون الأب عبر المنفذ فقط . يسمي هذا المفهوم المُكونات عديمة المُحتوي

مثال علي المُكونات عديمة المُحتوي لاحظ انها تقوم بتجميع المميزات و المنطق الخاص بها (تتبع إحداثيات الفأرة):

template
<MouseTracker v-slot="{ x, y }">
  Mouse is at: {{ x }}, {{ y }}
</MouseTracker>

في حين انه نمط مثير للإهتمام , يمكن تحقيق الكثير بإستخدام المُكونات عديمة المُحتوي بطريقة منظمة و مُرتبة خاصة مع إستخدام طريقة الواجهة التركيبية (Composition Api) , بدون الحاجة إلي إستخدام مُكونات متداخلة . سوف نعرف لاحقاً كيفية تطبيق اَلية عمل تتبع الفأرة نفسه كمُركب.

كما ذكرنا تظل المنافذ محددة النطاق مفيدة في الكثير من الحالات نحتاج إلي تجميع المميزات و المنطق و أيضاً عرض محتوي مرئي , مثل المثال <FancyList>

المنافذ has loaded