الأخطاء الثمانية الأكثر شيوعًا التي يرتكبها مطورو Ember.js
نشرت: 2022-03-11Ember.js هو إطار عمل شامل لبناء تطبيقات معقدة من جانب العميل. أحد مبادئها هو "الاصطلاح على التكوين" ، والاقتناع بأن هناك جزءًا كبيرًا جدًا من التطوير مشتركًا في معظم تطبيقات الويب ، وبالتالي فهي أفضل طريقة لحل معظم هذه التحديات اليومية. ومع ذلك ، فإن العثور على التجريد الصحيح ، وتغطية جميع الحالات ، يستغرق وقتًا ومدخلات من المجتمع بأكمله. كما يذهب المنطق ، من الأفضل أن تأخذ الوقت الكافي للحصول على حل للمشكلة الأساسية بشكل صحيح ، ثم نضعها في إطار العمل ، بدلاً من رفع أيدينا والسماح للجميع بالاعتناء بأنفسهم عندما يحتاجون إلى إيجاد حل.
يتطور موقع Ember.js باستمرار لجعل التطوير أسهل. ولكن ، كما هو الحال مع أي إطار عمل متقدم ، لا تزال هناك بعض المآزق التي قد يقع فيها مطورو Ember. مع المنشور التالي ، آمل أن أقدم خريطة لتفادي ذلك. دعنا نقفز مباشرة!
الخطأ الشائع رقم 1: توقع إطلاق خطاف النموذج عند تمرير جميع كائنات السياق
لنفترض أن لدينا المسارات التالية في تطبيقنا:
Router.map(function() { this.route('band', { path: 'bands/:id' }, function() { this.route('songs'); }); });
مسار band
له مقطع ديناميكي ، id
. عندما يتم تحميل التطبيق بعنوان URL مثل /bands/24
، يتم تمرير 24
إلى خطاف model
للمسار المقابل ، band
. يلعب خطاف النموذج دور إلغاء تسلسل المقطع لإنشاء كائن (أو مجموعة من الكائنات) يمكن استخدامه بعد ذلك في القالب:
// app/routes/band.js export default Ember.Route.extend({ model: function(params) { return this.store.find('band', params.id); // params.id is '24' } });
حتى الان جيدة جدا. ومع ذلك ، هناك طرق أخرى لإدخال المسارات غير تحميل التطبيق من شريط التنقل في المتصفح. يستخدم أحدهم link-to
مساعد من القوالب. يمر المقتطف التالي عبر قائمة العصابات وينشئ رابطًا إلى مسارات band
الخاصة بكل منها:
{{#each bands as |band|}} {{link-to band.name "band" band}} {{/each}}
الوسيطة الأخيرة لـ link-to ، band
، هي كائن يملأ المقطع الديناميكي للمسار ، وبالتالي يصبح معرفه هو مقطع id
المسار. الفخ الذي يقع فيه الكثير من الأشخاص هو أنه لا يتم استدعاء خطاف النموذج في هذه الحالة ، نظرًا لأن النموذج معروف بالفعل وتم تمريره. إنه منطقي وقد يحفظ طلبًا إلى الخادم ولكنه ، باعتراف الجميع ، لا حدسي. طريقة بارعة للتغلب على ذلك هي تمرير ، ليس الكائن نفسه ، ولكن معرفه:
{{#each bands as |band|}} {{link-to band.name "band" band.id}} {{/each}}
خطة إمبر للتخفيف
ستتوفر المكونات القابلة للتوجيه على Ember قريبًا ، ربما في الإصدار 2.1 أو 2.2. عندما يهبطون ، سيتم دائمًا استدعاء خطاف النموذج ، بغض النظر عن كيفية انتقال المرء إلى مسار ذي مقطع ديناميكي. اقرأ RFC المقابل هنا.
الخطأ الشائع رقم 2: نسيان أن أجهزة التحكم المدفوعة بالطريق هي عناصر فردية
تقوم المسارات في Ember.js بإعداد الخصائص على وحدات التحكم التي تعمل كسياق للقالب المقابل. وحدات التحكم هذه عبارة عن وحدات تحكم فردية ، وبالتالي فإن أي حالة محددة عليها تستمر حتى عندما لا تكون وحدة التحكم نشطة.
هذا شيء من السهل جدًا التغاضي عنه وقد عثرت عليه أيضًا. في حالتي ، كان لدي تطبيق كتالوج موسيقى به فرق موسيقية وأغاني. تشير علامة songCreationStarted
على وحدة التحكم في songs
إلى أن المستخدم قد بدأ في إنشاء أغنية لفرقة معينة. كانت المشكلة أنه إذا تحول المستخدم بعد ذلك إلى فرقة أخرى ، فإن قيمة songCreationStarted
تستمر ، ويبدو أن الأغنية نصف المكتملة كانت للفرقة الأخرى ، وهو أمر محير.
الحل هو إعادة ضبط خصائص وحدة التحكم يدويًا التي لا نريد أن نبقى فيها. أحد الأماكن الممكنة للقيام بذلك هو خطاف setupController
المقابل ، والذي يتم استدعاؤه في جميع الانتقالات بعد خطاف afterModel
(والذي ، كما يوحي اسمه ، يأتي بعد خطاف model
):
// app/routes/band.js export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); controller.set('songCreationStarted', false); } });
خطة إمبر للتخفيف
مرة أخرى ، سيحل فجر المكونات القابلة للتوجيه هذه المشكلة ، ووضع حد لوحدات التحكم تمامًا. تتمثل إحدى مزايا المكونات القابلة للتوجيه في أنها تتمتع بدورة حياة أكثر اتساقًا ويتم تفكيكها دائمًا عند الانتقال بعيدًا عن مساراتها. عند وصولهم ، سوف تختفي المشكلة المذكورة أعلاه.
الخطأ الشائع رقم 3: عدم استدعاء التطبيق الافتراضي في setupController
تحتوي المسارات في Ember على عدد قليل من خطاطيف دورة الحياة لتحديد السلوك الخاص بالتطبيق. لقد رأينا بالفعل model
يستخدم لجلب البيانات للقالب المقابل و setupController
، لإعداد وحدة التحكم ، سياق القالب.
هذا الأخير ، setupController
، لديه افتراضي معقول ، والذي يقوم بتعيين النموذج ، من خطاف model
كخاصية model
لوحدة التحكم:
// ember-routing/lib/system/route.js setupController(controller, context, transition) { if (controller && (context !== undefined)) { set(controller, 'model', context); } }
( context
هو الاسم الذي تستخدمه حزمة ember-routing
لما أسميه model
أعلاه)
يمكن تجاوز خطاف setupController
لعدة أغراض ، مثل إعادة ضبط حالة وحدة التحكم (كما هو الحال في Common Mistake No. 2 أعلاه). ومع ذلك ، إذا نسي المرء استدعاء التطبيق الأصلي الذي قمت بنسخه أعلاه في Ember.Route ، فيمكن للمرء أن يكون في جلسة حك الرأس الطويلة ، حيث لن يتم تعيين خصائص model
الخاصة بوحدة التحكم. لذلك اتصل دائمًا this._super(controller, model)
:
export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); // put the custom setup here } });
خطة إمبر للتخفيف
كما ذكرنا سابقًا ، ستختفي وحدات التحكم ، ومعها ، خطاف setupController
قريبًا ، لذا لن يشكل هذا المأزق تهديدًا بعد الآن. ومع ذلك ، هناك درس أكبر يمكن تعلمه هنا ، وهو أن تكون على دراية بالتطبيقات في الأجداد. تعتبر وظيفة init
، المحددة في Ember.Object
، أم جميع الكائنات في Ember ، مثالاً آخر عليك الانتباه إليه.
الخطأ الشائع رقم 4: استخدام this.modelFor
مع مسارات غير رئيسية
يقوم جهاز توجيه Ember بحل النموذج لكل مقطع مسار أثناء قيامه بمعالجة عنوان URL. لنفترض أن لدينا المسارات التالية في تطبيقنا:
Router.map({ this.route('bands', function() { this.route('band', { path: ':id' }, function() { this.route('songs'); }); }); });
بالنظر إلى عنوان URL لـ /bands/24/songs
، يُطلق على model
ربط bands
، bands.band
ثم bands.band.songs
، بهذا الترتيب. تحتوي واجهة API الخاصة بالمسار على طريقة مفيدة بشكل خاص ، modelFor
، والتي يمكن استخدامها في المسارات الفرعية لجلب النموذج من أحد المسارات الرئيسية ، حيث تم حل هذا النموذج بالتأكيد من خلال تلك النقطة.
على سبيل المثال ، الكود التالي هو طريقة صالحة لجلب كائن النطاق في مسار bands.band
:
// app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { var bands = this.modelFor('bands'); return bands.filterBy('id', params.id); } });
ومع ذلك ، فإن الخطأ الشائع هو استخدام اسم مسار في modelF لأنه ليس أصل المسار. إذا تم تغيير المسارات من المثال أعلاه بشكل طفيف:
Router.map({ this.route('bands'); this.route('band', { path: 'bands/:id' }, function() { this.route('songs'); }); });
ستتعطل طريقتنا في جلب النطاق المحدد في عنوان URL ، لأن مسار bands
لم يعد أصلًا وبالتالي لم يتم حل نموذجها.
// app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { var bands = this.modelFor('bands'); // `bands` is undefined return bands.filterBy('id', params.id); // => error! } });
الحل هو استخدام modelFor
فقط للمسارات الرئيسية ، واستخدام وسائل أخرى لاسترداد البيانات الضرورية عندما لا يمكن استخدام modelFor
، مثل الجلب من المخزن.
// app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { return this.store.find('band', params.id); } });
الخطأ الشائع رقم 5: الخطأ في السياق كإجراء مكون يتم تفعيله
لطالما كانت المكونات المتداخلة من أصعب أجزاء Ember التي يمكن التفكير فيها. مع إدخال معلمات الكتلة في Ember 1.10 ، تم تخفيف الكثير من هذا التعقيد ، ولكن في العديد من المواقف ، لا يزال من الصعب أن نرى في لمحة ما المكون الذي سيتم تشغيل إجراء ما ، تم إطلاقه من مكون فرعي.
لنفترض أن لدينا مكون band-list
band-list-items
، ويمكننا تمييز كل فرقة كمفضلة في القائمة.
// app/templates/components/band-list.hbs {{#each bands as |band|}} {{band-list-item band=band faveAction="setAsFavorite"}} {{/each}}
يتم تمرير اسم الإجراء الذي يجب استدعاؤه عندما ينقر المستخدم على الزر إلى مكون band-list-item
، ويصبح قيمة خاصية faveAction
الخاصة به.
دعنا الآن نرى القالب وتعريف المكون لعنصر band-list-item
:
// app/templates/components/band-list-item.hbs <div class="band-name">{{band.name}}</div> <button class="fav-button" {{action "faveBand"}}>Fave this</button>
// app/components/band-list-item.js export default Ember.Component.extend({ band: null, faveAction: '', actions: { faveBand: { this.sendAction('faveAction', this.get('band')); } } });
عندما ينقر المستخدم على زر "Fave this" ، يتم تشغيل إجراء faveBand
، مما يؤدي إلى faveAction
الخاص بالمكون الذي تم تمريره ( setAsFavorite
، في الحالة المذكورة أعلاه) ، على المكون الرئيسي ، band-list
.

يؤدي ذلك إلى تعثر الكثير من الأشخاص نظرًا لأنهم يتوقعون أن يتم تشغيل الإجراء بنفس الطريقة التي يتم بها تشغيل الإجراءات من القوالب المدفوعة بالمسار ، على وحدة التحكم (ثم تظهر فقاعات على المسارات النشطة). وما يزيد الأمر سوءًا هو عدم تسجيل أي رسالة خطأ ؛ المكون الرئيسي يبتلع الخطأ فقط.
القاعدة العامة هي أن الإجراءات يتم إطلاقها على السياق الحالي. في حالة القوالب غير المكونة ، يكون هذا السياق هو وحدة التحكم الحالية ، بينما في حالة قوالب المكونات ، يكون المكون الرئيسي (إذا كان هناك واحدًا) ، أو مرة أخرى وحدة التحكم الحالية إذا لم يكن المكون متداخلًا.
لذلك في الحالة المذكورة أعلاه ، سيتعين على مكون band-list
إعادة إطلاق الإجراء الذي تم استلامه من band-list-item
أجل إرساله إلى وحدة التحكم أو المسار.
// app/components/band-list.js export default Ember.Component.extend({ bands: [], favoriteAction: 'setFavoriteBand', actions: { setAsFavorite: function(band) { this.sendAction('favoriteAction', band); } } });
إذا تم تحديد band-list
في قالب bands
، فيجب معالجة الإجراء setFavoriteBand
في وحدة التحكم في bands
أو مسار bands
(أو أحد مساراتها الأصلية).
خطة إمبر للتخفيف
يمكنك أن تتخيل أن هذا يصبح أكثر تعقيدًا إذا كان هناك المزيد من مستويات التداخل (على سبيل المثال ، من خلال وجود مكون fav-button
داخل عنصر band-list-item
). عليك أن تحفر حفرة من خلال عدة طبقات من الداخل لإخراج رسالتك ، وتحديد الأسماء ذات المعنى في كل مستوى ( setAsFavorite
، و favoriteAction
، و faveAction
، وما إلى ذلك)
يتم تبسيط هذا الأمر من خلال "إجراءات RFC المحسّنة" ، والمتوفرة بالفعل في الفرع الرئيسي ، ومن المحتمل أن يتم تضمينها في 1.13.
سيتم بعد ذلك تبسيط المثال أعلاه إلى:
// app/templates/components/band-list.hbs {{#each bands as |band|}} {{band-list-item band=band setFavBand=(action "setFavoriteBand")}} {{/each}}
// app/templates/components/band-list-item.hbs <div class="band-name">{{band.name}}</div> <button class="fav-button" {{action "setFavBand" band}}>Fave this</button>
الخطأ الشائع رقم 6: استخدام خصائص الصفيف كمفاتيح تابعة
تعتمد خصائص Ember المحسوبة على خصائص أخرى ، وتحتاج هذه التبعية إلى تحديد صريح من قبل المطور. لنفترض أن لدينا خاصية isAdmin
يجب أن تكون صحيحة إذا وفقط إذا كان أحد الأدوار هو admin
. هذه هي الطريقة التي يمكن أن يكتبها المرء:
isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles')
مع التعريف أعلاه ، يتم إبطال قيمة isAdmin
فقط إذا تغير كائن مصفوفة roles
نفسه ، ولكن ليس إذا تمت إضافة عناصر إلى المصفوفة الموجودة أو إزالتها. هناك صيغة خاصة لتعريف أن الإضافات وعمليات الإزالة يجب أن تؤدي أيضًا إلى إعادة الحساب:
isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles.[]')
الخطأ الشائع رقم 7: عدم استخدام الأساليب الصديقة للمراقبين
دعنا نوسع المثال (الذي تم إصلاحه الآن) من الخطأ الشائع رقم 6 ، وننشئ فئة مستخدم في تطبيقنا.
var User = Ember.Object.extend({ initRoles: function() { var roles = this.get('roles'); if (!roles) { this.set('roles', []); } }.on('init'), isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles.[]') });
عندما نضيف دور admin
لمثل هذا User
، فإننا نتفاجأ:
var user = User.create(); user.get('isAdmin'); // => false user.get('roles').push('admin'); user.get('isAdmin'); // => false ?
تكمن المشكلة في أن المراقبين لن يطلقوا النار (وبالتالي لن يتم تحديث الخصائص المحسوبة) إذا تم استخدام طرق جافا سكريبت للمخزون. قد يتغير هذا إذا تحسن الاعتماد العالمي لـ Object.observe
في المتصفحات ، ولكن حتى ذلك الحين ، يتعين علينا استخدام مجموعة الأساليب التي يوفرها Ember. في الحالة الحالية ، pushObject
هو المكافئ الملائم للمراقب لـ push
:
user.get('roles').pushObject('admin'); user.get('isAdmin'); // => true, finally!
الخطأ الشائع رقم 8: التحول في خصائص المكونات
تخيل أن لدينا مكون star-rating
والذي يعرض تصنيف العنصر ويسمح بإعداد تصنيف العنصر. يمكن أن يكون التقييم لأغنية أو كتاب أو مهارة مراوغة لاعب كرة القدم.
يمكنك استخدامه على النحو التالي في القالب الخاص بك:
{{#each songs as |song|}} {{star-rating item=song rating=song.rating}} {{/each}}
دعنا نفترض أيضًا أن المكون يعرض النجوم ، نجمة كاملة واحدة لكل نقطة ، ونجوم فارغة بعد ذلك ، حتى الحد الأقصى من التصنيف. عندما يتم النقر على نجمة ، set
تشغيل إجراء معين على وحدة التحكم ، ويجب تفسيره على أنه يريد المستخدم تحديث التصنيف. يمكننا كتابة الكود التالي لتحقيق ذلك:
// app/components/star-rating.js export default Ember.Component.extend({ item: null, rating: 0, (...) actions: { set: function(newRating) { var item = this.get('item'); item.set('rating', newRating); return item.save(); } } });
سيؤدي ذلك إلى إنجاز المهمة ، ولكن هناك مشكلتان في ذلك. أولاً ، يفترض أن العنصر الذي تم تمريره له خاصية rating
، وبالتالي لا يمكننا استخدام هذا المكون لإدارة مهارة مراوغة Leo Messi (حيث يمكن تسمية هذه الخاصية score
).
ثانيًا ، يغير تصنيف العنصر في المكون. يؤدي هذا إلى سيناريوهات يصعب فيها معرفة سبب تغيير خاصية معينة. تخيل أن لدينا مكونًا آخر في نفس القالب حيث يتم استخدام هذا التصنيف أيضًا ، على سبيل المثال ، لحساب متوسط نقاط لاعب كرة القدم.
شعار التخفيف من تعقيد هذا السيناريو هو "خفض البيانات ، رفع الإجراءات" (DDAU). يجب تمرير البيانات لأسفل (من المسار إلى وحدة التحكم إلى المكونات) ، بينما يجب أن تستخدم المكونات إجراءات لإخطار سياقها بالتغييرات في هذه البيانات. إذن كيف يجب تطبيق DDAU هنا؟
دعنا نضيف اسم إجراء يجب إرساله لتحديث التصنيف:
{{#each songs as |song|}} {{star-rating item=song rating=song.rating setAction="updateRating"}} {{/each}}
ثم استخدم هذا الاسم لإرسال الإجراء:
// app/components/star-rating.js export default Ember.Component.extend({ item: null, rating: 0, (...) actions: { set: function(newRating) { var item = this.get('item'); this.sendAction('setAction', { item: this.get('item'), rating: newRating }); } } });
أخيرًا ، يتم التعامل مع الإجراء في اتجاه المنبع ، بواسطة وحدة التحكم أو المسار ، وهذا هو المكان الذي يتم فيه تحديث تصنيف العنصر:
// app/routes/player.js export default Ember.Route.extend({ actions: { updateRating: function(params) { var skill = params.item, rating = params.rating; skill.set('score', rating); return skill.save(); } } });
عندما يحدث هذا ، يتم نشر هذا التغيير لأسفل من خلال الربط الذي تم تمريره إلى مكون star-rating
، ويتغير عدد النجوم الكاملة المعروضة نتيجة لذلك.
بهذه الطريقة ، لا تحدث الطفرة في المكونات ، وبما أن الجزء الوحيد المحدد للتطبيق هو التعامل مع الإجراء في المسار ، فإن إمكانية إعادة استخدام المكون لا تتأثر.
يمكننا أيضًا استخدام نفس المكون لمهارات كرة القدم:
{{#each player.skills as |skill|}} {{star-rating item=skill rating=skill.score setAction="updateSkill"}} {{/each}}
الكلمات الأخيرة
من المهم أن نلاحظ أن بعض (معظم؟) الأخطاء التي رأيتها يرتكبها الناس (أو ارتكبها بنفسي) ، بما في ذلك الأخطاء التي كتبت عنها هنا ، ستختفي أو يتم تخفيفها بشكل كبير في وقت مبكر من سلسلة 2.x من Ember.js.
تتم معالجة ما تبقى من خلال اقتراحاتي أعلاه ، لذلك بمجرد أن تقوم بالتطوير في Ember 2.x ، لن يكون لديك أي عذر لارتكاب المزيد من الأخطاء! إذا كنت ترغب في الحصول على هذه المقالة بتنسيق pdf ، فانتقل إلى مدونتي وانقر فوق الارتباط الموجود أسفل المنشور.
عني
لقد جئت إلى عالم الواجهة الأمامية مع Ember.js منذ عامين ، وأنا هنا لأبقى. أصبحت متحمسًا جدًا لـ Ember لدرجة أنني بدأت في التدوين بشكل مكثف في كل من مشاركات الضيف وفي مدونتي الخاصة ، بالإضافة إلى التقديم في المؤتمرات. حتى أنني كتبت كتابًا ، Rock and Roll مع Ember.js ، لأي شخص يريد تعلم Ember. يمكنك تنزيل فصل من هنا.