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

يعرض مؤلف المادة ، الذي ننشر ترجمته اليوم ، تحليل بعض الأخطاء الشائعة التي يرتكبها أولئك الذين يطورون التطبيقات على Vue.js.
الآثار الجانبية داخل الخصائص المحسوبة
الخصائص المحسوبة هي آلية Vue.js مريحة للغاية تتيح لك تنظيم العمل مع أجزاء الحالة التي تعتمد على أجزاء الحالة الأخرى. يجب استخدام الخصائص المحسوبة فقط لعرض البيانات المخزنة في حالة تعتمد على بيانات أخرى من الحالة. إذا تبين أنك تستدعي بعض الأساليب داخل الخصائص المحسوبة أو تكتب بعض القيم إلى متغيرات الحالة الأخرى ، فقد يعني ذلك أنك تقوم بشيء خطأ. النظر في مثال.
export default { data() { return { array: [1, 2, 3] }; }, computed: { reversedArray() { return this.array.reverse();
إذا حاولنا طباعة
array
و
reversedArray
، فسوف نلاحظ أن كلا الصفيفين يحتويان على نفس القيم.
: [ 3, 2, 1 ] : [ 3, 2, 1 ]
وهذا بسبب تعديل الخاصية
.reverse()
المحسوبة خاصية
array
الأصلي عن طريق استدعاء الأسلوب
.reverse()
. هذا مثال بسيط إلى حد ما يوضح سلوك النظام غير المتوقع. نلقي نظرة على مثال آخر.
افترض أن لدينا مكونًا يعرض معلومات مفصلة حول سعر السلع أو الخدمات المضمّنة في ترتيب معين.
export default { props: { order: { type: Object, default: () => ({}) } }, computed:{ grandTotal() { let total = (this.order.total + this.order.tax) * (1 - this.order.discount); this.$emit('total-change', total) return total.toFixed(2); } } }
لقد أنشأنا هنا خاصية محسوبة تعرض التكلفة الإجمالية للطلب ، بما في ذلك الضرائب والخصومات. نظرًا لأننا نعلم أن قيمة الطلب الإجمالية تتغير هنا ، فيمكننا محاولة رفع حدث يخطر المكون الأصل بالتغيير
grandTotal
.
<price-details :order="order" @total-change="totalChange"> </price-details> export default { // methods: { totalChange(grandTotal) { if (this.isSpecialCustomer) { this.order = { ...this.order, discount: this.order.discount + 0.1 }; } } } };
الآن تخيل أنه في بعض الأحيان ، على الرغم من نادرًا للغاية ، تنشأ مواقف نعمل فيها مع عملاء خاصين. نمنح هؤلاء العملاء خصمًا إضافيًا بنسبة 10٪. يمكننا محاولة تغيير كائن
order
وزيادة حجم الخصم بإضافة
0.1
إلى خاصية
discount
الخاصة به.
هذا ، ومع ذلك ، سوف يؤدي إلى خطأ سيء.
رسالة خطأحساب قيمة الطلب غير صحيح لعميل خاصفي حالة مماثلة ، يحدث ما يلي: الخاصية المحسوبة باستمرار ، في حلقة لا نهائية ، "إعادة فرزها". نقوم بتغيير الخصم ، وتتفاعل الخاصية المحسوبة مع هذا ، وتعيد حساب التكلفة الإجمالية للطلب وتولد حدثًا. عند معالجة هذا الحدث ، يزيد الخصم مرة أخرى ، وهذا يؤدي إلى إعادة حساب الخاصية المحسوبة ، وهكذا - إلى ما لا نهاية.
قد يبدو لك أنه لا يمكن ارتكاب مثل هذا الخطأ في تطبيق حقيقي. ولكن هل هو حقا كذلك؟ سيكون من الصعب للغاية تصحيح نصنا (إذا حدث شيء مثل هذا في هذا التطبيق). مثل هذا الخطأ سيكون من الصعب للغاية تتبع. والحقيقة هي أنه لكي يحدث هذا الخطأ ، من الضروري أن يتم تقديم الطلب من قبل مشتر خاص ، وقد يكون لهذا الطلب 1000 طلب منتظم.
تغيير الخصائص المتداخلة
في بعض الأحيان ، قد يتم إغراء أحد المطورين بتحرير شيء ما في خاصية ما من
props
، وهو كائن أو صفيف. هذه الرغبة يمكن أن تمليها حقيقة أنه "بسيط جداً" للقيام بذلك. ولكن هل يستحق كل هذا العناء؟ النظر في مثال.
<template> <div class="hello"> <div>Name: {{product.name}}</div> <div>Price: {{product.price}}</div> <div>Stock: {{product.stock}}</div> <button @click="addToCart" :disabled="product.stock <= 0">Add to card</button> </div> </template> export default { name: "HelloWorld", props: { product: { type: Object, default: () => ({}) } }, methods: { addToCart() { if (this.product.stock > 0) { this.$emit("add-to-cart"); this.product.stock--; } } } };
هنا لدينا مكون
Product.vue
، الذي يعرض اسم المنتج وقيمته وكمية البضائع التي لدينا. يعرض المكون أيضًا زرًا يسمح للمشتري بوضع البضائع في السلة. قد يبدو أنه سيكون من السهل جدًا والمريح تقليل قيمة خاصية
product.stock
بعد النقر فوق الزر. للقيام بذلك بسيط حقا. ولكن إذا قمت بذلك ، فقد تواجه العديد من المشكلات:
- نقوم بإجراء تغيير (تغيير) في الخاصية ولا نبلغ أي شيء إلى الكيان الأصل.
- هذا يمكن أن يؤدي إلى سلوك غير متوقع للنظام ، أو ما هو أسوأ من ذلك ، إلى ظهور أخطاء غريبة.
- نقدم بعض المنطق في مكون
product
، والذي ربما لا يجب أن يكون موجودًا فيه.
تخيل موقفًا افتراضيًا يصادف فيه مطور آخر الشفرة أولاً ويرى المكون الأصل.
<template> <Product :product="product" @add-to-cart="addProductToCart(product)"></Product> </template> import Product from "./components/Product"; export default { name: "App", components: { Product }, data() { return { product: { name: "Laptop", price: 1250, stock: 2 } }; }, methods: { addProductToCart(product) { if (product.stock > 0) { product.stock--; } } } };
قد يكون تفكير هذا المطور على النحو التالي: "على ما يبدو ، أحتاج إلى تقليل
addProductToCart
طريقة
addProductToCart
" ولكن إذا تم ذلك ، فسنواجه خطأً صغيراً. إذا ضغطت الآن على الزر ، فسيتم تقليل كمية البضائع ليس بنسبة 1 ، ولكن بمقدار 2.
تخيل أن هذه حالة خاصة عندما يتم إجراء هذا الفحص فقط للبضائع النادرة أو فيما يتعلق بتوفر خصم خاص. إذا دخلت هذه الشفرة حيز الإنتاج ، فيمكن أن ينتهي كل شيء بحقيقة أن عملائنا سوف يشترون نسختين بدلاً من نسخة واحدة من المنتج.
إذا بدا هذا المثال غير مقنع لك ، تخيل سيناريو آخر. فليكن النموذج الذي يملأه المستخدم. نقوم بتمرير جوهر
user
إلى النموذج كخاصية وسنقوم بتحرير اسم وعنوان البريد الإلكتروني للمستخدم. قد يظهر الرمز الموضح أدناه بأنه "صحيح".
من السهل البدء مع
user
باستخدام توجيه
v-model
. Vue.js يسمح بذلك. لماذا لا تفعل ذلك؟ فكر في الأمر:
- ماذا لو كان هناك حاجة إلى إضافة زر "إلغاء" إلى النموذج ، والنقر فوق أي إلغاء التغييرات التي تم إجراؤها؟
- ماذا لو فشل اتصال الخادم؟ كيفية التراجع عن تغييرات كائن
user
؟ - هل نريد حقًا عرض الاسم المتغير وعنوان البريد الإلكتروني في المكون الأصل قبل حفظ التغييرات المقابلة؟
قد تتمثل طريقة بسيطة "لإصلاح" المشكلة في استنساخ كائن
user
قبل إرساله كخاصية:
<user-form :user="{...user}">
على الرغم من أن هذا قد ينجح ، إلا أننا نتفادى المشكلة ، لكن لا نحلها. يجب أن يكون لمكون
UserForm
الخاص بنا حالة محلية خاصة به. هنا هو ما يمكننا القيام به.
<template> <div> <input placeholder="Email" type="email" v-model="form.email"/> <input placeholder="Name" v-model="form.name"/> <button @click="onSave">Save</button> <button @click="onCancel">Save</button> </div> </template> export default { props: { user: { type: Object, default: () => ({}) } }, data() { return { form: {} } }, methods: { onSave() { this.$emit('submit', this.form) }, onCancel() { this.form = {...this.user} this.$emit('cancel') } } watch: { user: { immediate: true, handler: function(userFromProps){ if(userFromProps){ this.form = { ...this.form, ...userFromProps } } } } } }
على الرغم من أن هذا الرمز يبدو معقدًا إلى حد ما ، إلا أنه أفضل من الإصدار السابق. انها تسمح لك للتخلص من المشاكل المذكورة أعلاه. نتوقع (
watch
) التغييرات في خاصية
user
ونسخها في بيانات
form
الداخلي. نتيجة لذلك ، أصبح النموذج الآن خاصًا به ، ونحصل على الميزات التالية:
- يمكنك التراجع عن التغييرات عن طريق إعادة تعيين النموذج:
this.form = {...this.user}
. - لدينا حالة معزولة للنموذج.
- لا تؤثر تصرفاتنا على المكون الرئيسي في حالة عدم حاجتنا إليه.
- نحن نتحكم في ما يحدث عندما نحاول حفظ التغييرات.
الوصول المباشر إلى المكونات الأصل
إذا كان أحد المكونات يشير إلى مكون آخر وأجرى بعض الإجراءات عليه ، فقد يؤدي ذلك إلى تناقضات وأخطاء ، مما قد يؤدي إلى سلوك غريب للتطبيق وفي ظهور مكونات ذات صلة به.
فكر في مثال بسيط للغاية - مكون يقوم بتنفيذ قائمة منسدلة. تخيل أن لدينا مكون قائمة
dropdown
(
dropdown-menu
ومكون
dropdown-menu
(تابع). عندما ينقر المستخدم على عنصر قائمة معين ، نحتاج إلى إغلاق
dropdown-menu
. يتم إخفاء وإظهار هذا المكون بواسطة المكون الأصل من
dropdown
. نلقي نظرة على مثال.
انتبه إلى طريقة
selectOption
. على الرغم من أن هذا نادرًا ما يحدث ، فقد يرغب شخص ما في الاتصال مباشرة بـ
$parent
. هذه الرغبة يمكن تفسيرها من خلال حقيقة أن الأمر بسيط للغاية.
للوهلة الأولى ، قد يبدو أن هذا الرمز يعمل بشكل صحيح. ولكن هنا يمكنك رؤية بعض المشاكل:
- ماذا لو قمنا بتغيير
showMenu
أو showMenu
؟ لن تتمكن القائمة المنسدلة من الإغلاق ولن يتم تحديد أي من عناصرها. - ماذا لو كنت في حاجة لتحريك
dropdown-menu
باستخدام نوع من الانتقال؟
هذا الرمز ، مرة أخرى ، بسبب التغيير في
$parent
، لن يعمل. لم يعد المكون
dropdown
هو الأصل في
dropdown-menu
. الآن الأصل
dropdown-menu
هو مكون
transition
.
يتم تمرير الخصائص أسفل التسلسل الهرمي للمكون ، ويتم تمرير الأحداث لأعلى. تحتوي هذه الكلمات على معنى النهج الصحيح لحل مشكلتنا. هنا مثال تم تعديله للأحداث.
الآن ، بفضل استخدام الأحداث ، لم يعد المكون الفرعي مرتبطًا بالمكون الأصل. يمكننا تغيير الخصائص بحرية مع البيانات الموجودة في المكون الأصل واستخدام التحولات المتحركة. ومع ذلك ، قد لا نفكر في كيفية تأثير الكود الخاص بنا على المكون الأصل. نحن ببساطة إخطار هذا المكون بما حدث. في هذه الحالة ، يتخذ المكون
dropdown
نفسه قرارات بشأن كيفية التعامل مع اختيار المستخدم لعنصر القائمة وتشغيل إغلاق القائمة.
النتائج
أقصر رمز ليس هو الأكثر نجاحًا دائمًا. غالبًا ما تكون تقنيات التطوير التي تتضمن نتائج "بسيطة وسريعة" معيبة. من أجل استخدام أي لغة برمجة أو مكتبة أو إطار عمل بشكل صحيح ، تحتاج إلى الصبر والوقت. هذا صحيح بالنسبة لـ Vue.js.
أعزائي القراء! هل واجهت أي مشاكل في الممارسة ، مثل تلك التي تمت مناقشتها في هذه المقالة؟
