Vue.js للمبتدئين الدرس 11: علامات التبويب ، ناقل الحدث العالمي

اليوم ، في الدرس الحادي عشر الذي يختتم هذا البرنامج التعليمي Vue Fundamentals ، سنتحدث عن كيفية تنظيم محتوى صفحة التطبيق باستخدام علامات التبويب. سنناقش هنا ناقل الحدث العالمي - آلية بسيطة لنقل البيانات داخل التطبيق.







درس المبتدئين Vue.js 1: المثال Vue

Vue.js للمبتدئين ، الدرس 2: سمات الربط

Vue.js الدرس 3: العرض الشرطي

Vue.js للمبتدئين الدرس 4: عرض القوائم

Vue .js للمبتدئين الدرس 5: معالجة الأحداث

Vue.js للمبتدئين الدرس 6: فئات وأنماط الربط

Vue.js للمبتدئين الدرس 7: الخصائص المحسوبة

Vue.js للمبتدئين الدرس 8: المكونات

Vue. js للمبتدئين الدرس 9: أحداث مخصصة

Vue.js للمبتدئين الدرس 10: النماذج



الغرض من الدرس



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



الكود الأولي



هكذا يبدو محتوى الملف في هذه المرحلة من العمل index.html:



<div id="app">
  <div class="cart">
    <p>Cart({{ cart.length }})</p>
  </div>

  <product :premium="premium" @add-to-cart="updateCart"></product>
</div>


في main.jsهناك التعليمة البرمجية التالية:



Vue.component('product', {
  props: {
    premium: {
      type: Boolean,
      required: true
    }
  },
  template: `
  <div class="product">

    <div class="product-image">
      <img :src="image" />
    </div>

    <div class="product-info">
      <h1>{{ title }}</h1>
      <p v-if="inStock">In stock</p>
      <p v-else>Out of Stock</p>
      <p>Shipping: {{ shipping }}</p>

      <ul>
        <li v-for="(detail, index) in details" :key="index">{{ detail }}</li>
      </ul>
      <div
        class="color-box"
        v-for="(variant, index) in variants"
        :key="variant.variantId"
        :style="{ backgroundColor: variant.variantColor }"
        @mouseover="updateProduct(index)"
      ></div>

      <button
        @click="addToCart"
        :disabled="!inStock"
        :class="{ disabledButton: !inStock }"
      >
        Add to cart
      </button>

    </div>

    <div>
      <h2><font color="#3AC1EF">Reviews</font></h2>
      <p v-if="!reviews.length">There are no reviews yet.</p>
      <ul>
        <li v-for="review in reviews">
        <p>{{ review.name }}</p>
        <p>Rating: {{ review.rating }}</p>
        <p>{{ review.review }}</p>
        </li>
      </ul>
    </div>

    <product-review @review-submitted="addReview"></product-review>   
  
    </div>
  `,
  data() {
    return {
      product: 'Socks',
      brand: 'Vue Mastery',
      selectedVariant: 0,
      details: ['80% cotton', '20% polyester', 'Gender-neutral'],
      variants: [
        {
          variantId: 2234,
          variantColor: 'green',
          variantImage: './assets/vmSocks-green.jpg',
          variantQuantity: 10
        },
        {
          variantId: 2235,
          variantColor: 'blue',
          variantImage: './assets/vmSocks-blue.jpg',
          variantQuantity: 0
        }
      ],
      reviews: []
    }
  },
    methods: {
      addToCart() {
        this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId);
      },
      updateProduct(index) {
        this.selectedVariant = index;
      },
      addReview(productReview) {
        this.reviews.push(productReview)
      }
    },
    computed: {
      title() {
        return this.brand + ' ' + this.product;
      },
      image() {
        return this.variants[this.selectedVariant].variantImage;
      },
      inStock() {
        return this.variants[this.selectedVariant].variantQuantity;
      },
      shipping() {
        if (this.premium) {
          return "Free";
        } else {
          return 2.99
        }
      }
    }
})

Vue.component('product-review', {
  template: `
    <form class="review-form" @submit.prevent="onSubmit">

      <p v-if="errors.length">
        <b>Please correct the following error(s):</b>
        <ul>
          <li v-for="error in errors">{{ error }}</li>
        </ul>
      </p>

      <p>
        <label for="name">Name:</label>
        <input id="name" v-model="name">
      </p>
      
      <p>
        <label for="review">Review:</label>      
        <textarea id="review" v-model="review"></textarea>
      </p>
      
      <p>
        <label for="rating">Rating:</label>
        <select id="rating" v-model.number="rating">
          <option>5</option>
          <option>4</option>
          <option>3</option>
          <option>2</option>
          <option>1</option>
        </select>
      </p>
          
      <p>
        <input type="submit" value="Submit">  
      </p>    

    </form>

  `,
  data() {
    return {
      name: null,
      review: null,
      rating: null,
      errors: []
    }
  },
  methods: {
    onSubmit() {
      if(this.name && this.review && this.rating) {
        let productReview = {
          name: this.name,
          review: this.review,
          rating: this.rating
        }
        this.$emit('review-submitted', productReview)
        this.name = null
        this.review = null
        this.rating = null
      } else {
        if(!this.name) this.errors.push("Name required.")
        if(!this.review) this.errors.push("Review required.")
        if(!this.rating) this.errors.push("Rating required.")
      }
    }
  }
})

var app = new Vue({
  el: '#app',
  data: {
    premium: true,
    cart: []
  },
  methods: {
    updateCart(id) {
      this.cart.push(id);
    }
  }
})


هذا ما يبدو عليه التطبيق الآن.





صفحة التطبيق



مهمة



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



حل المشكلة



لحل مشكلتنا ، يمكننا إضافة نظام علامات تبويب إلى الصفحة. Reviewsسيعرض أحدهم ، مع العنوان ، المراجعات. والثاني ، مع عنوان Make a Review، سيعرض نموذجًا لإرسال المراجعات.



إنشاء مكون يقوم بتنفيذ نظام علامات التبويب



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



Vue.component('product-tabs', {
  template: `
    <div>
      <span class="tab" v-for="(tab, index) in tabs" :key="index">{{ tab }}</span>
    </div>
  `,
  data() {
    return {
      tabs: ['Reviews', 'Make a Review']      
    }
  }
})


الآن هذا مجرد مكون فارغ سننتهي منه قريبًا. في الوقت الحالي ، دعنا نناقش بإيجاز ما يتم تقديمه في هذا الرمز.



تحتوي بيانات المكون على مصفوفة tabsتحتوي على السلاسل التي نستخدمها كرؤوس علامات التبويب. يستخدم قالب المكون بناءًا يقوم بإنشاء عنصر يحتوي على السلسلة المقابلة v-forلكل tabsعنصر مصفوفة <span>. سيبدو شكل هذا المكون في هذه المرحلة من العمل عليه مثل المكون الموضح أدناه.





مكون علامات تبويب المنتج في المرحلة الأولى من العمل عليه



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



@click="selectedTab = tab"


ستكتب الخاصية سلاسل مطابقة لرؤوس علامات التبويب.



بمعنى ، إذا نقر المستخدم على علامة التبويب Reviews، selectedTabفستتم كتابة سلسلة نصية Reviews. إذا قمت بالنقر فوق علامة التبويب Make a Review، selectedTabفسيتم تضمين الخط Make a Review.



هذا ما سيبدو عليه رمز المكون الكامل الآن.



Vue.component('product-tabs', {
  template: `
    <div>    
      <ul>
        <span class="tab" 
              v-for="(tab, index) in tabs" 
              @click="selectedTab = tab"
        >{{ tab }}</span>
      </ul> 
    </div>
  `,
  data() {
    return {
      tabs: ['Reviews', 'Make a Review'],
      selectedTab: 'Reviews'  //    @click
    }
  }
})


ربط الفصل بعلامة تبويب نشطة



يجب أن يكون المستخدم الذي يعمل مع واجهة تستخدم علامات التبويب على دراية بعلامة التبويب النشطة. يمكنك تنفيذ آلية مشابهة باستخدام ربط الفئة بالعناصر <span>المستخدمة لعرض أسماء علامات التبويب:



:class="{ activeTab: selectedTab === tab }"


هذا هو ملف CSS الذي يحدد نمط الفصل المستخدم هنا activeTab. هذا ما يبدو عليه هذا النمط:



.activeTab {
  color: #16C0B0;
  text-decoration: underline;
}


وهنا أسلوب الفصل tab:



.tab {
  margin-left: 20px;
  cursor: pointer;
}


إذا شرحنا البناء أعلاه بلغة بسيطة ، فقد اتضح أن النمط المحدد للفئة يتم تطبيقه على علامة التبويب activeTab، في حالة selectedTabتساوي tab. نظرًا لأن selectedTabاسم علامة التبويب التي نقر عليها المستخدم للتو مكتوب ، .activeTabفسيتم تطبيق النمط على وجه التحديد على علامة التبويب النشطة.



بمعنى آخر ، عندما ينقر المستخدم على علامة التبويب الأولى ، tabسيتم تحديد موقعه Reviews، وستتم كتابة الأمر نفسه selectedTab. نتيجة لذلك ، سيتم تطبيق النمط على علامة التبويب الأولى .activeTab.



ستبدو الآن عناوين علامة التبويب على الصفحة كما يلي.





العنوان المميز لعلامة التبويب النشطة



يبدو أن كل شيء يعمل كما هو متوقع في هذه المرحلة ، حتى نتمكن من المضي قدمًا.



العمل على قالب المكون



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



لنفكر فيما يجب أن يظهر للمستخدم إذا نقر على علامة التبويب Reviews. هذه ، بالطبع ، مراجعات المنتج. لذلك ، دعنا ننقل الكود لعرض المراجعات من قالب المكوّن إلى productقالب المكوّن product-tabs، مع وضع هذا الرمز أسفل البناء المستخدم لعرض رؤوس علامات التبويب. هذا ما سيبدو عليه قالب المكون الآن product-tabs:



template: `
  <div>    
    <ul>
      <span class="tab"
            :class="{ activeTab: selectedTab === tab }" 
            v-for="(tab, index) in tabs" 
            @click="selectedTab = tab"
      >{{ tab }}</span>
    </ul> 
    <div>
      <p v-if="!reviews.length">There are no reviews yet.</p>
      <ul>
        <li v-for="review in reviews">
        <p>{{ review.name }}</p>
        <p>Rating: {{ review.rating }}</p>
        <p>{{ review.review }}</p>
        </li>
      </ul>
    </div>
  </div>
`


لاحظ أننا تخلصنا من العلامة <h2><font color="#3AC1EF">لأننا لم نعد بحاجة إلى عرض العنوان Reviewsأعلى قائمة المراجعات. بدلاً من هذا العنوان ، سيتم عرض عنوان علامة التبويب المقابلة.



لكن نقل رمز القالب وحده لا يكفي لتقديم ملاحظات. reviewsيتم تخزين المصفوفة التي تُستخدم بياناتها لعرض المراجعات كجزء من بيانات المكون product. نحتاج إلى تمرير هذه المصفوفة إلى المكون product-tabsباستخدام آلية props. دعنا نضيف ما product-tabsيلي إلى الكائن بالخيارات المستخدمة أثناء الإنشاء :



props: {
  reviews: {
    type: Array,
    required: false
  }
}


دعنا نمرر مصفوفة reviewsمن مكون productإلى مكون product-tabsباستخدام productالبناء التالي في القالب :



<product-tabs :reviews="reviews"></product-tabs>


لنفكر الآن فيما يجب عرضه على الصفحة إذا نقر المستخدم على عنوان علامة التبويب Make a Review. هذا ، بالطبع ، نموذج لتقديم الملاحظات. من أجل تحضير المشروع لمزيد من العمل عليه ، دعنا ننقل رمز اتصال المكون product-reviewمن قالب المكون productإلى القالب product-tabs. دعنا نضع الكود التالي أسفل العنصر <div>المستخدم لعرض قائمة المراجعات:



<div>
  <product-review @review-submitted="addReview"></product-review>
</div>


إذا نظرت إلى صفحة التطبيق الآن ، ستجد أن قائمة المراجعات والنموذج معروضان عليها أسفل رؤوس علامات التبويب.





مرحلة وسيطة من العمل على الصفحة



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



العرض الشرطي لعناصر الصفحة



الآن وقد أعددنا العناصر الرئيسية لقالب المكون product-tabs، فقد حان الوقت لإنشاء نظام يسمح لك بعرض عناصر صفحة مختلفة بناءً على عنوان علامة التبويب الذي نقر عليه المستخدم.



بيانات المكون لها خاصية بالفعل selectedTab. يمكننا استخدامه في توجيه v-showلتقديم ما ينتمي إلى كل علامة تبويب مشروطًا.



لذلك ، إلى العلامة <div>التي تحتوي على رمز إنشاء قائمة المراجعات ، يمكننا إضافة البنية التالية:



v-show="selectedTab === 'Reviews'"


بفضلها ، سيتم عرض قائمة المراجعات عندما تكون علامة التبويب نشطة Reviews.



وبالمثل ، سنضيف ما يلي إلى العلامة <div>التي تحتوي على كود اتصال المكون product-review:



v-show="selectedTab === 'Make a Review'"


سيؤدي هذا إلى عرض النموذج فقط عندما تكون علامة التبويب نشطة Make a Review.



هذا ما سيبدو عليه قالب المكون الآن product-tabs:



template: `
  <div>    
    <ul>
      <span class="tab"
            :class="{ activeTab: selectedTab === tab }" 
            v-for="(tab, index) in tabs" 
            @click="selectedTab = tab"
      >{{ tab }}</span>
    </ul> 
    <div v-show="selectedTab === 'Reviews'">
      <p v-if="!reviews.length">There are no reviews yet.</p>
      <ul>
        <li v-for="review in reviews">
        <p>{{ review.name }}</p>
        <p>Rating: {{ review.rating }}</p>
        <p>{{ review.review }}</p>
        </li>
      </ul>
    </div>
    <div v-show="selectedTab === 'Make a Review'">
      <product-review @review-submitted="addReview"></product-review>
    </div>
  </div>
`


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





يؤدي النقر فوق علامات التبويب إلى إخفاء بعض العناصر وعرض البعض الآخر.



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



حل المشكلة بإرسال الملاحظات



إذا نظرت إلى وحدة تحكم أدوات مطور المستعرض الآن ، يمكنك رؤية تحذير.





تحذير وحدة التحكم من



الواضح أن النظام لا يمكنه اكتشاف الطريقةaddReview. ماذا حدث له؟



للإجابة على هذا السؤال ، تذكر أنهaddReviewطريقة معلنة في أحد المكوناتproduct. يجب استدعائه إذا كان المكونproduct-review(وهذا مكون فرعي للمكونproduct) يولد حدثًاreview-submitted:



<product-review @review-submitted="addReview"></product-review>


هذه هي الطريقة التي عمل بها كل شيء قبل نقل مقتطف الشفرة أعلاه إلى المكون product-tabs. والآن عنصرا productهو عنصر الطفل product-tabs، و product-reviewالآن أنها ليست "الطفل"، وهو عنصر product، ولكن لها "حفيد".



تم تصميم الكود الخاص بنا الآن للتفاعل product-reviewمع المكون الرئيسي. لكنها الآن لم تعد مكونًا product. نتيجة لذلك ، اتضح أنه لكي يعمل النموذج بشكل صحيح ، نحتاج إلى إعادة تشكيل كود المشروع.



إعادة هيكلة كود المشروع



من أجل ضمان اتصال مكونات الأحفاد بـ "أجدادهم" ، أو من أجل إقامة اتصال بين مكونات من نفس المستوى ، غالبًا ما يتم استخدام آلية تسمى ناقل الحدث العالمي.



إن Global Event Bus هي قناة اتصال يمكن استخدامها لنقل المعلومات بين المكونات. وهو ، في الواقع ، مجرد مثيل Vue يتم إنشاؤه دون تمريره ككائن به خيارات. لنقم بإنشاء ناقل الحدث:



var eventBus = new Vue()


سينتقل هذا الرمز إلى المستوى الأعلى من الملف main.js.



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



الآن في المكون product-review، في الطريقة onSubmit، يوجد سطر مثل هذا:



this.$emit('review-submitted', productReview)


دعنا نستبدله مع التالي ، eventBusبدلاً من ذلك this:



eventBus.$emit('review-submitted', productReview)


بعد ذلك ، لم تعد بحاجة إلى الاستماع إلى حدث review-submittedالمكون product-review. لذلك ، سنقوم بتغيير كود هذا المكون في قالب المكون product-tabsإلى ما يلي:



<product-review></product-review>


productيمكن الآن إزالة الطريقة من المكون addReview. بدلاً من ذلك ، سوف نستخدم البناء التالي:



eventBus.$on('review-submitted', productReview => {
  this.reviews.push(productReview)
})


سنتحدث أدناه عن كيفية استخدامه في أحد المكونات ، ولكن في الوقت الحالي ، سنصف باختصار ما يحدث فيه. يشير هذا البناء إلى أنه عند eventBusإنشاء حدث ما review-submitted، فأنت بحاجة إلى أخذ البيانات التي تم تمريرها في هذا الحدث (أي ، - productReview) ووضعها في مصفوفة reviewsالمكون product. في الواقع ، هذا مشابه جدًا لما تم القيام به حتى الآن بطريقة addReviewلم نعد بحاجة إليها. لاحظ أن مقتطف الشفرة أعلاه يستخدم وظيفة السهم. تستحق هذه اللحظة تغطية أكثر تفصيلاً.



أسباب استخدام دالة السهم



نحن هنا نستخدم صيغة دالة السهم التي تم تقديمها في ES6. النقطة المهمة هي أن سياق وظيفة السهم مرتبط بالسياق الأصلي. أي عندما نستخدم ، داخل هذه الوظيفة ، كلمة رئيسية this، فهي تعادل الكلمة الأساسية thisالتي تتوافق مع الكيان الذي يحتوي على وظيفة السهم.



يمكن إعادة كتابة هذا الرمز بدون استخدام وظائف الأسهم ، ولكن بعد ذلك تحتاج إلى تنظيم الربط this:



eventBus.$on('review-submitted', function (productReview) {
  this.reviews.push(productReview)
}.bind(this))


استكمال المشروع



لقد وصلنا إلى هدفنا تقريبًا. كل ما يتبقى هو إيجاد مكان لجزء من الكود الذي يوفر رد فعل على الحدث review-submitted. productيمكن أن تصبح الوظيفة مكانًا في أحد المكونات mounted:



mounted() {
  eventBus.$on('review-submitted', productReview => {
    this.reviews.push(productReview)
  })
}


ما هي هذه الوظيفة؟ هذا هو رابط دورة الحياة الذي يتم استدعاؤه مرة واحدة بعد تثبيت المكون في DOM. الآن ، بعد تثبيت المكون product، سينتظر حدوث الأحداث review-submitted. بعد إنشاء مثل هذا الحدث ، سيتم إضافة ما تم تمريره في هذا الحدث إلى بيانات المكون ، أي - productReview.



إذا حاولت الآن ترك مراجعة حول المنتج باستخدام النموذج ، فقد اتضح أن هذه المراجعة معروضة في المكان الذي يجب أن تكون فيه.





يعمل النموذج كما ينبغي



ناقل الحدث ليس هو الحل الأفضل لتوصيل المكونات



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



مع نمو التطبيق ، يمكن أن يكون نظام إدارة الحالة القائم على Vuex مفيدًا جدًا . إنه نمط إدارة حالة التطبيق والمكتبة.



ورشة عمل



إضافة علامات تبويب Shippingو للمشروع Details، والتي، على التوالي، وعرض تكلفة توصيل المشتريات والمعلومات حول البضائع.



  • إليك نموذج يمكنك استخدامه لحل هذه المشكلة.
  • هذا هو الحل للمشكلة.


النتيجة



إليك ما تعلمته في هذا البرنامج التعليمي:



  • يمكنك استخدام أدوات العرض الشرطي لتنظيم آلية علامات التبويب.
  • , Vue, .
  • — . . — Vuex.


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



إذا كنت قد أكملت هذه الدورة للتو ، فيرجى مشاركة انطباعاتك.



درس المبتدئين Vue.js 1: المثال Vue

Vue.js للمبتدئين ، الدرس 2: سمات الربط

Vue.js الدرس 3: العرض الشرطي

Vue.js للمبتدئين الدرس 4: عرض القوائم

Vue .js للمبتدئين ، الدرس 5: معالجة الأحداث

Vue.js للمبتدئين ، الدرس 6: فئات وأنماط الربط

Vue.js للمبتدئين ، الدرس 7: الخصائص المحسوبة

Vue.js للمبتدئين ، الدرس 8: المكونات

Vue.js للمبتدئين ، الدرس 9: الأحداث المخصصة

Vue.js للمبتدئين ، الدرس 10: النماذج






All Articles