ما الجديد في Vue 3؟
نشرت: 2022-03-10مع إصدار Vue 3 ، يتعين على المطورين إجراء الترقية من Vue 2 لأنه يأتي مع عدد قليل من الميزات الجديدة المفيدة للغاية في بناء مكونات سهلة القراءة ويمكن صيانتها وطرق محسّنة لهيكلة تطبيقنا في Vue. سنلقي نظرة على بعض هذه الميزات في هذه المقالة.
في نهاية هذا البرنامج التعليمي ، سوف يقوم القراء بما يلي:
- تعرف على
provide / inject
وكيفية استخدامه. - لديك فهم أساسي لـ Teleport وكيفية استخدامه.
- تعرف على الأجزاء وكيفية استخدامها.
- تعرف على التغييرات التي تم إجراؤها على Global Vue API.
- تعرف على التغييرات التي تم إجراؤها على 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.
يقبل مكون النقل الآني اثنين من الدعائم التي تحدد سلوك هذا المكون وهما ؛
-
to
تقبل هذه الخاصية إما اسم فئة أو معرف أو عنصر أو سمة data- *. يمكننا أيضًا جعل هذه القيمة ديناميكية من خلال تمرير a:to
prop بدلاًto
عنصر النقل الآني وتغييره ديناميكيًا. -
: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
؛
فتات
باستخدام 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
من أعلى ؛
- عند إضافة
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
تأثير على المكون ؛
- لإصلاح ذلك ، أضف
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