ما الجديد في Vue 3؟

نشرت: 2022-03-10
ملخص سريع يأتي Vue 3 مع الكثير من الميزات الجديدة المثيرة للاهتمام والتغييرات على بعض الميزات الحالية التي تهدف إلى جعل التطوير مع إطار العمل أسهل كثيرًا ويمكن صيانته. في هذه المقالة ، سنلقي نظرة على بعض هذه الميزات الجديدة وكيفية البدء بها. سنقوم أيضًا بإلقاء نظرة على بعض التغييرات التي تم إجراؤها على الميزات الحالية.

مع إصدار Vue 3 ، يتعين على المطورين إجراء الترقية من Vue 2 لأنه يأتي مع عدد قليل من الميزات الجديدة المفيدة للغاية في بناء مكونات سهلة القراءة ويمكن صيانتها وطرق محسّنة لهيكلة تطبيقنا في Vue. سنلقي نظرة على بعض هذه الميزات في هذه المقالة.

في نهاية هذا البرنامج التعليمي ، سوف يقوم القراء بما يلي:

  1. تعرف على provide / inject وكيفية استخدامه.
  2. لديك فهم أساسي لـ Teleport وكيفية استخدامه.
  3. تعرف على الأجزاء وكيفية استخدامها.
  4. تعرف على التغييرات التي تم إجراؤها على Global Vue API.
  5. تعرف على التغييرات التي تم إجراؤها على Events API.

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

provide / inject

في Vue 2.x ، لدينا props تجعل من السهل تمرير البيانات (سلسلة ، مصفوفات ، كائنات ، إلخ) من مكون رئيسي مباشرةً إلى مكون فرعي. ولكن أثناء التطوير ، وجدنا غالبًا حالات احتجنا فيها إلى تمرير البيانات من المكون الرئيسي إلى مكون متداخل بعمق والذي كان من الصعب القيام به مع props . نتج عن ذلك استخدام Vuex Store و Event Hub ، وفي بعض الأحيان تمرير البيانات عبر المكونات المتداخلة بشدة. لنلقِ نظرة على تطبيق بسيط ؛

من المهم ملاحظة أن Vue 2.2.0 يأتي أيضًا مع provide / inject لم يوصى باستخدامه في رمز التطبيق العام.

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { color: "", colors: ["red", "blue", "green"], }; }, }; </script>
 # childComponent.vue <template> <div class="hello"> <h1>{{ msg }}</h1> <color-selector :color="color"></color-selector> </div> </template> <script> import colorSelector from "@/components/colorComponent.vue"; export default { name: "HelloWorld", components: { colorSelector, }, props: { msg: String, color: String, }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
 # colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { props: { color: String, }, }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

هنا ، لدينا صفحة مقصودة بها قائمة منسدلة تحتوي على قائمة من الألوان ونقوم بتمرير color المحدد إلى childComponent.vue كدعم. يحتوي هذا المكون الفرعي أيضًا على خاصية msg التي تقبل نصًا ليتم عرضه في قسم النموذج. أخيرًا ، يحتوي هذا المكون على مكون فرعي ( colorComponent.vue ) يقبل خاصية color من المكون الرئيسي المستخدم في تحديد فئة النص في هذا المكون. هذا مثال على تمرير البيانات عبر جميع المكونات.

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

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { colors: ["red", "blue", "green"], }; }, provide: { color: 'blue' } }; </script>

ولكن في الحالات التي تحتاج فيها إلى تمرير خاصية مثيل مكون provide ، فإننا نستخدم وضع الوظيفة حتى يكون ذلك ممكنًا ؛

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "blue", colors: ["red", "blue", "green"], }; }, provide() { return { color: this.selectedColor, }; }, }; </script>

نظرًا لأننا لا نحتاج إلى دعامات color في كل من childComponent.vue و colorComponent.vue ، فإننا نتخلص منها. الشيء الجيد في استخدام provide هو أن المكون الرئيسي لا يحتاج إلى معرفة المكون الذي يحتاج إلى الخاصية التي يوفرها.

للاستفادة من هذا في المكون الذي يحتاجه في هذه الحالة ، نقوم بهذا الأمر colorComponent.vue ؛

 # colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

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

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

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; import { computed } from "vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "", todos: ["Feed a cat", "Buy tickets"], colors: ["red", "blue", "green"], }; }, provide() { return { color: computed(() => this.selectedColor), }; }, }; </script>

هنا ، نقوم باستيراد اللون computed وتمرير اللون selectedColor لدينا بحيث يمكن أن يكون رد الفعل والتحديث حيث يختار المستخدم لونًا مختلفًا. عندما تمرر متغيرًا إلى الطريقة المحسوبة ، فإنه يعيد كائنًا له value . تحتفظ هذه الخاصية بقيمة المتغير الخاص بك ، لذلك في هذا المثال ، سيتعين علينا تحديث colorComponent.vue لتبدو هكذا ؛

 # colorComponent.vue <template> <p :class="[color.value]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

هنا ، نقوم بتغيير color إلى color.value لتمثيل التغيير بعد جعل color تفاعليًا باستخدام الطريقة computed . في هذه المرحلة ، ستتغير class النص في هذا المكون دائمًا كلما تغير لون selectedColor في المكون الرئيسي.

المزيد بعد القفز! أكمل القراءة أدناه ↓

النقل الفضائي

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

يسمح لنا النقل الفوري بإخراج مكون من موضعه الأصلي في مستند ، من #app الحاوية الافتراضية ، يتم تغليف تطبيقات Vue ونقله إلى أي عنصر موجود على الصفحة التي يتم استخدامها. من الأمثلة الجيدة استخدام Teleport لنقل مكون رأس من داخل #app div إلى header . من المهم ملاحظة أنه يمكنك فقط الانتقال الآني إلى العناصر الموجودة خارج Vue DOM.

رسالة خطأ في وحدة التحكم عند الانتقال الفوري إلى عنصر غير صالح
رسالة خطأ النقل الآني في وحدة التحكم: رسالة خطأ هدف النقل الآني غير صالح في الجهاز. (معاينة كبيرة)

يقبل مكون النقل الآني اثنين من الدعائم التي تحدد سلوك هذا المكون وهما ؛

  1. to
    تقبل هذه الخاصية إما اسم فئة أو معرف أو عنصر أو سمة data- *. يمكننا أيضًا جعل هذه القيمة ديناميكية من خلال تمرير a :to prop بدلاً to عنصر النقل الآني وتغييره ديناميكيًا.
  2. :disabled
    تقبل هذه الخاصية قيمة Boolean ويمكن استخدامها لتبديل ميزة النقل الآني على عنصر أو مكون. يمكن أن يكون هذا مفيدًا لتغيير موضع العنصر ديناميكيًا.

مثال مثالي لاستخدام Teleport يبدو هكذا ؛

 # index.html** <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <title> <%= htmlWebpackPlugin.options.title %> </title> </head> <!-- add container to teleport to --> <header class="header"></header> <body> <noscript> <strong >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript> <div></div> <!-- built files will be auto injected --> </body> </html>

في ملف index.html الافتراضي في تطبيق Vue الخاص بك ، نضيف عنصر header لأننا نريد نقل مكون الرأس الخاص بنا إلى تلك النقطة في تطبيقنا. أضفنا أيضًا فئة إلى هذا العنصر من أجل التصميم ولسهولة الرجوع إليها في مكون النقل الآني الخاص بنا.

 # Header.vue** <template> <teleport to="header"> <h1 class="logo">Vue 3 </h1> <nav> <router-link to="/">Home</router-link> </nav> </teleport> </template> <script> export default { name: "app-header", }; </script> <style> .header { display: flex; align-items: center; justify-content: center; } .logo { margin-right: 20px; } </style>

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

 # App.vue <template> <router-view /> <app-header></app-header> </template> <script> import appHeader from "@/components/Header.vue"; export default { components: { appHeader, }, }; </script>

في هذا الملف ، نقوم باستيراد مكون الرأس ووضعه في القالب حتى يمكن رؤيته في تطبيقنا.

الآن إذا فحصنا عنصر تطبيقنا ، فسنلاحظ أن مكون الرأس الخاص بنا موجود داخل عنصر header ؛

مكون الرأس في DevTools
مكون الرأس في DevTools (معاينة كبيرة)

فتات

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

مع Vue 3 ، تم تقديم ميزة جديدة تسمى Fragments وتسمح هذه الميزة للمطورين بالحصول على عناصر متعددة في ملف القالب الجذر الخاص بهم. لذلك مع Vue 2.x ، هذا هو الشكل الذي سيبدو عليه مكون حاوية حقل الإدخال ؛

 # inputComponent.vue <template> <div> <label :for="label">label</label> <input :type="type" : :name="label" /> </div> </template> <script> export default { name: "inputField", props: { label: { type: String, required: true, }, type: { type: String, required: true, }, }, }; </script> <style></style>

هنا ، لدينا مكون عنصر نموذج بسيط يقبل خاصيتين ، label type ، وقسم القالب لهذا المكون ملفوف في div. هذه ليست مشكلة بالضرورة ولكن إذا كنت تريد أن يكون حقل التسمية والإدخال داخل عنصر form مباشرةً. باستخدام Vue 3 ، يمكن للمطورين إعادة كتابة هذا المكون بسهولة ليبدو هكذا ؛

 # inputComponent.vue <template class="testingss"> <label :for="label">{{ label }}</label> <input :type="type" : :name="label" /> </template>

باستخدام عقدة جذر واحدة ، تُنسب السمات دائمًا إلى عقدة الجذر وتُعرف أيضًا باسم السمات غير الخاصة . إنها أحداث أو سمات يتم تمريرها إلى مكون ليس له خصائص مقابلة محددة في props أو عمليات emits . ومن الأمثلة على هذه السمات class id . ومع ذلك ، فمن الضروري تحديد أي من العناصر في مكون العقدة متعددة الجذور يجب أن يُنسب إليه.

إليك ما يعنيه هذا باستخدام inputComponent.vue من أعلى ؛

  1. عند إضافة class إلى هذا المكون في المكون الرئيسي ، يجب تحديد المكون الذي ستنسب إليه هذه class وإلا فلن يكون للسمة أي تأثير.
 <template> <div class="home"> <div> <input-component class="awesome__class" label="name" type="text" ></input-component> </div> </div> </template> <style> .awesome__class { border: 1px solid red; } </style>

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

رسالة خطأ في المحطة عندما لا يتم توزيع السمات
ظهور رسالة خطأ في الجهاز عند عدم توزيع السمات (معاينة كبيرة)

وليس border تأثير على المكون ؛

المكون بدون توزيع السمة
مكون بدون توزيع السمات (معاينة كبيرة)
  1. لإصلاح ذلك ، أضف v-bind="$attrs" إلى العنصر الذي تريد توزيع هذه السمات عليه ؛
 <template> <label :for="label" v-bind="$attrs">{{ label }}</label> <input :type="type" : :name="label" /> </template>

هنا ، نخبر Vue أننا نريد توزيع السمات على عنصر label مما يعني أننا نريد تطبيق awesome__class عليه. الآن ، إذا فحصنا عنصرنا في المتصفح ، فسنرى أنه تمت إضافة الفصل الآن إلى label ، ومن ثم أصبح الحد الآن حول الملصق.

مكون مع توزيع السمات
مكون مع توزيع السمات (معاينة كبيرة)

API العالمية

لم يكن من غير المألوف رؤية Vue.component أو Vue.use في ملف main.js لتطبيق Vue. تُعرف هذه الأنواع من الأساليب بواجهات برمجة التطبيقات العالمية ويوجد عدد كبير منها في Vue 2.x. تتمثل إحدى تحديات هذه الطريقة في أنها تجعل من المستحيل عزل وظائف معينة عن مثيل واحد من تطبيقك (إذا كان لديك أكثر من مثيل واحد في تطبيقك) دون أن يؤثر ذلك على التطبيقات الأخرى نظرًا لأنه يتم تثبيتها جميعها على Vue. هذا ما اعنيه؛

 Vue.directive('focus', { inserted: el => el.focus() }) Vue.mixin({ /* ... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })

بالنسبة إلى الكود أعلاه ، من المستحيل الإشارة إلى أن توجيه Vue مرتبط بـ app1 و Mixin مع app2 ولكن بدلاً من ذلك ، كلاهما متاح في التطبيقين.

يأتي Vue 3 مع واجهة برمجة تطبيقات عالمية جديدة في محاولة لإصلاح هذا النوع من المشاكل من خلال تقديم createApp . تقوم هذه الطريقة بإرجاع مثيل جديد لتطبيق Vue. يعرض مثيل التطبيق مجموعة فرعية من واجهات برمجة التطبيقات العالمية الحالية. باستخدام هذا ، سيتم الآن نقل جميع واجهات برمجة التطبيقات (المكون ، المزيج ، التوجيه ، الاستخدام ، إلخ) التي تغير Vue من Vue 2.x إلى مثيلات التطبيق الفردية والآن ، يمكن أن يكون لكل مثيل من تطبيق Vue وظائف فريدة لـ دون التأثير على التطبيقات الأخرى الحالية.

الآن ، يمكن إعادة كتابة الكود أعلاه كـ ؛

 const app1 = createApp({}) const app2 = createApp({}) app1.directive('focus', { inserted: el => el.focus() }) app2.mixin({ /* ... */ })

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

أحداث API

يعد استخدام Event Bus أحد أكثر الطرق شيوعًا التي اعتمدها المطورون لتمرير البيانات بين المكونات التي ليس لها علاقة أب لطفل بخلاف استخدام Vuex Store. أحد أسباب شيوع هذه الطريقة هو سهولة البدء بها ؛

 # eventBus.js const eventBus = new Vue() export default eventBus;

بعد ذلك ، سيكون الشيء التالي هو استيراد هذا الملف إلى main.js لجعله متاحًا عالميًا في تطبيقنا أو لاستيراده في الملفات التي تحتاجها ؛

 # main.js import eventBus from 'eventBus' Vue.prototype.$eventBus = eventBus

الآن ، يمكنك إرسال الأحداث والاستماع إلى الأحداث المنبعثة مثل هذا ؛

 this.$eventBus.$on('say-hello', alertMe) this.$eventBus.$emit('pass-message', 'Event Bus says Hi')

هناك الكثير من التعليمات البرمجية لـ Vue المليئة برمز مثل هذا. ومع ذلك ، مع Vue 3 ، سيكون من المستحيل القيام بذلك لأنه تمت إزالة $on و $off و $once ولكن $emit لا يزال متاحًا لأنه مطلوب من مكون الأطفال إرسال الأحداث إلى المكونات الأصلية الخاصة بهم. قد يكون البديل عن ذلك هو استخدام provide / inject أو أي من مكتبات الجهات الخارجية الموصى بها.

خاتمة

في هذه المقالة ، قمنا بتغطية كيف يمكنك تمرير البيانات من مكون رئيسي إلى مكون فرعي متداخل بعمق باستخدام زوج provide / inject . لقد نظرنا أيضًا في كيفية تغيير موضع ونقل المكونات من نقطة في تطبيقنا إلى نقطة أخرى. شيء آخر نظرنا إليه هو مكون العقدة متعددة الجذور وكيفية ضمان توزيع السمات بحيث تعمل بشكل صحيح. أخيرًا ، قمنا أيضًا بتغطية التغييرات التي تم إجراؤها على Events API و Global API.

مزيد من الموارد

  • "وظائف مصنع JavaScript مع ES6 +" ، إريك إليوت ، متوسط
  • "استخدام Event Bus لمشاركة الدعائم بين مكونات Vue ،" Kingsley Silas ، CSS-Tricks
  • استخدام وسائل نقل متعددة على نفس الهدف ، مستندات Vue.js
  • السمات غير الدعائية ، مستندات Vue.js
  • العمل مع التفاعلية ، مستندات Vue.js
  • teleport ، مستندات Vue.js
  • شظايا ، مستندات Vue.js
  • 2.x بناء الجملة ومستندات Vue.js