المطورين الزاويون مدينون بالكثير إلى zone.js. إنها ، على سبيل المثال ، تساعد على تحقيق سهولة سحرية في العمل مع Angular. في الواقع ، دائمًا ، عندما تحتاج فقط إلى تغيير بعض الممتلكات ، ونحن نقوم بتغييره دون التفكير في أي شيء ، تعيد Angular عرض المكونات المقابلة. نتيجة لذلك ، فإن ما يراه المستخدم يحتوي دائمًا على أحدث المعلومات. هذا رائع فقط.
أود هنا استكشاف بعض الجوانب المتعلقة بكيفية استخدام المحول البرمجي Ivy الجديد (الذي ظهر في Angular 9) إلى حد كبير في تسهيل رفض استخدام zone.js.

بالتخلي عن هذه المكتبة ، تمكنت من زيادة أداء التطبيق الزاوي الذي يعمل تحت عبء ثقيل بشكل ملحوظ. في الوقت نفسه ، تمكنت من تنفيذ الآليات التي أحتاجها باستخدام أدوات تزيين TypeScript ، والتي أدت إلى موارد نظام إضافية قليلة جدًا.
يرجى ملاحظة أن النهج لتحسين التطبيقات Angular ، المقدمة في هذه المقالة ، هو ممكن فقط لأن يتم تمكين Angular Ivy و AOT بشكل افتراضي. هذا المقال مكتوب لأغراض تعليمية ، وهو لا يهدف إلى تعزيز النهج المقدم فيه لتطوير المشاريع الزاوي.
لماذا قد تحتاج إلى استخدام الزاوي دون zone.js؟
قبل المتابعة ، دعنا نسأل سؤالًا مهمًا: "هل يستحق التخلص من zone.js ، بالنظر إلى أن هذه المكتبة تساعدنا على إعادة تقديم القوالب دون بذل الكثير من الجهد؟" بالطبع ، هذه المكتبة مفيدة جدا. لكن ، كالعادة ، عليك أن تدفع ثمن كل شيء.
إذا كان التطبيق الخاص بك يحتوي على متطلبات أداء محددة ، فإن تعطيل zone.js يمكن أن يساعد في تلبية هذه المتطلبات. مثال على تطبيق يكون فيه الأداء حاسمًا هو مشروع يتم تحديث واجهته كثيرًا. في حالتي ، تحول هذا المشروع إلى تطبيق تداول في الوقت الفعلي. يتلقى جزء العميل الخاص به الرسائل باستمرار عبر بروتوكول WebSocket. يجب أن يتم عرض البيانات من هذه الرسائل في أسرع وقت ممكن.
إزالة zone.js من الزاوي
الزاوي يمكن بسهولة جدا للعمل دون zone.js. للقيام بذلك ، يجب عليك أولاً التعليق أو حذف أمر الاستيراد المقابل ، الموجود في ملف
polyfills.ts
.
علق خارج أمر استيراد zone.jsالتالي - تحتاج إلى تجهيز وحدة الجذر مع الخيارات التالية:
platformBrowserDynamic() .bootstrapModule(AppModule, { ngZone: 'noop' }) .catch(err => console.error(err));
الزاوي اللبلاب: تغييرات الكشف الذاتي مع ɵdetectChanges و ɵmarkDirty
قبل أن نتمكن من البدء في إنشاء أداة تزيين TypeScript ، نحتاج إلى معرفة كيفية السماح لك Ivy باستدعاء عملية اكتشاف تغييرات المكونات ، وجعلها متسخة ، وتجاوز zone.js و DI.
تتوفر الآن وظيفتان إضافيتان لنا ، تم تصديرهما من
@angular/core
. هذه هي
ɵdetectChanges
و
ɵmarkDirty
. لا تزال
ɵ
للاستخدام الداخلي وهما غير مستقرين - يقع الرمز at في بداية
ɵ
.
دعونا ننظر في كيفية استخدام هذه الميزات.
ɵ ɵ وظيفةالقطر
تتيح لك هذه الوظيفة وضع علامة على أحد المكونات ، مما يجعلها "قذرة" ، وهذا في حاجة إلى إعادة التقديم. إنها ، إذا لم يتم وضع علامة على العنصر "متسخ" قبل أن يتم استدعاؤه ، فإنها تخطط لبدء عملية الكشف عن التغيير.
import { ɵmarkDirty as markDirty } from '@angular/core'; @Component({...}) class MyComponent { setTitle(title: string) { this.title = title; markDirty(this); } }
et etdetectChanges وظيفة
توضح الوثائق الداخلية ذات الزوايا أنه ، لأسباب تتعلق بالأداء ، يجب ألا تستخدم
ɵdetectChanges
. بدلاً من ذلك ، يوصى باستخدام الدالة
ɵmarkDirty
.
ɵdetectChanges
الدالة
ɵdetectChanges
بشكل متزامن عملية اكتشاف التغييرات في أحد المكونات والمكونات الفرعية التابعة له.
import { ɵdetectChanges as detectChanges } from '@angular/core'; @Component({...}) class MyComponent { setTitle(title: string) { this.title = title; detectChanges(this); } }
الكشف عن التغييرات تلقائيًا باستخدام TypeScript decorator
على الرغم من أن الميزات التي توفرها Angular تزيد من قابلية التطوير للتطوير من خلال السماح لـ DI بالتجول ، إلا أنه لا يزال بالإمكان إحباط المبرمج من حقيقة أنه يحتاج إلى استيراد واستدعاء هذه الوظائف بنفسه لبدء عملية اكتشاف التغيير.
من أجل تبسيط البدء التلقائي لاكتشاف التغيير ، يمكنك كتابة أداة TypeScript ، والتي ستحل هذه المشكلة بشكل مستقل. بالطبع ، هناك بعض القيود هنا ، والتي سنناقشها أدناه ، ولكن في حالتي تبين أن هذا النهج هو بالضبط ما أحتاجه.
t تقديم الديكورobserved
من أجل اكتشاف التغييرات ، وبذل أقل جهد ممكن ، سنقوم بإنشاء ديكور يمكن تطبيقه بثلاث طرق. وهي قابلة للتطبيق على الكيانات التالية:
- إلى طرق متزامن.
- أشياء يمكن ملاحظتها.
- إلى الأشياء العادية.
النظر في بضعة أمثلة صغيرة. في جزء التعليمات البرمجية التالي ، نحن نطبق decorator
@observed
على كائن
state
وعلى أسلوب
changeTitle
:
export class Component { title = ''; @observed() state = { name: '' }; @observed() changeTitle(title: string) { this.title = title; } changeName(name: string) { this.state.name = name; } }
- للتحقق من التغييرات التي تطرأ على كائن
state
، نستخدم كائنًا وكيلًا يعترض التغييرات على الكائن ويستدعي الإجراء الخاص باكتشاف التغييرات. - نحن
changeTitle
طريقة changeTitle
خلال تطبيق دالة تستدعي هذه الطريقة أولاً ثم تبدأ عملية الكشف عن التغيير.
وهنا مثال مع
BehaviorSubject
:
export class AppComponent { @observed() show$ = new BehaviorSubject(true); toggle() { this.show$.next(!this.show$.value); } }
في حالة الأشياء التي يمكن ملاحظتها ، يبدو استخدام الديكور أكثر تعقيدًا. وهي تحتاج إلى الاشتراك في الكائن المرصود ووضع علامة على المكون على أنه "متسخ" في الاشتراك ، لكنك تحتاج أيضًا إلى محو الاشتراك. للقيام بذلك ، نقوم
ngOnInit
تعيين
ngOnInit
و
ngOnDestroy
للاشتراك وتنظيفه لاحقًا.
reat إنشاء ديكور
هنا هو توقيع الديكور
observed
:
export function observed() { return function( target: object, propertyKey: string, descriptor?: PropertyDescriptor ) {} }
كما ترون ،
descriptor
هو معلمة اختيارية. هذا لأننا نحتاج إلى تطبيق الديكور على كل من الأساليب والخصائص. إذا كانت المعلمة موجودة ، فهذا يعني أن الديكور يتم تطبيقه على الطريقة. في هذه الحالة ، نقوم بهذا:
بعد ذلك ، تحتاج إلى التحقق من نوع العقار الذي نتعامل معه. يمكن أن يكون كائن يمكن ملاحظته أو كائن عادي. هنا سنستخدم API Angular داخلي آخر. في اعتقادي ، ليس المقصود للاستخدام في التطبيقات العادية (آسف!).
نحن نتحدث عن خاصية
ɵcmp
، والتي تتيح الوصول إلى الخصائص التي تتم معالجتها بواسطة Angular بعد تعريفها. يمكننا استخدامها لتجاوز أساليب
onDestroy
و
onDestroy
.
const getCmp = type => (type).ɵcmp; const cmp = getCmp(target.constructor); const onInit = cmp.onInit || noop; const onDestroy = cmp.onDestroy || noop;
من أجل وضع علامة على خاصية واحدة ليتم مراقبتها ، نستخدم
ReflectMetadata
قيمته على
true
. نتيجة لذلك ، سنعرف أننا نحتاج إلى مراقبة الخاصية عند تهيئة المكون:
Reflect.set(target, propertyKey, true);
الآن حان الوقت لتجاوز ربط
onInit
والتحقق من الخصائص عند إنشاء مثيل المكون:
cmp.onInit = function() { checkComponentProperties(this); onInit.call(this); };
نحدد وظيفة
checkComponentProperties
، والتي ستتجاوز خصائص المكون ، وتصفيتها وفقًا للقيمة
Reflect.set
مسبقًا باستخدام
Reflect.set
:
const checkComponentProperties = (ctx) => { const props = Object.getOwnPropertyNames(ctx); props.map((prop) => { return Reflect.get(target, prop); }).filter(Boolean).forEach(() => { checkProperty.call(ctx, propertyKey); }); };
وظيفة
checkProperty
ستكون مسؤولة عن تزيين الممتلكات الفردية. أولاً ، نتحقق مما إذا كانت الخاصية يمكن ملاحظتها أو أنها كائن عادي. إذا كان هذا كائنًا يمكن ملاحظته ، فنحن نشترك فيه ونضيف الاشتراك إلى قائمة الاشتراكات المخزنة في المكون لتلبية احتياجاته الداخلية.
const checkProperty = function(name: string) { const ctx = this; if (ctx[name] instanceof Observable) { const subscriptions = getSubscriptions(ctx); subscriptions.add(ctx[name].subscribe(() => { markDirty(ctx); })); } else {
إذا كانت الخاصية كائنًا عاديًا ، فسنقوم بتحويلها إلى كائن وكيل
markDirty
في وظيفة
handler
:
const handler = { set(obj, prop, value) { obj[prop] = value; ɵmarkDirty(ctx); return true; } }; ctx[name] = new Proxy(ctx, handler);
أخيرًا ، تحتاج إلى مسح الاشتراك بعد إتلاف المكون:
cmp.onDestroy = function() { const ctx = this; if (ctx[subscriptionsSymbol]) { ctx[subscriptionsSymbol].unsubscribe(); } onDestroy.call(ctx); };
لا يمكن أن يسمى احتمالات هذا الديكور شاملة. أنها لا تغطي جميع الاستخدامات المحتملة التي قد تظهر في تطبيق كبير. على سبيل المثال ، هذه استدعاءات لوظائف القالب التي تُرجع كائنات يمكن ملاحظتها. لكنني أعمل على ذلك.
على الرغم من هذا ، فإن الديكور أعلاه يكفي لمشروعي الصغير. ستجد رمزها الكامل في نهاية المادة.
تحليل نتائج تسريع التطبيق
الآن وقد تحدثنا قليلاً عن آليات Ivy الداخلية ، وكيفية إنشاء ديكور باستخدام هذه الآليات ، فقد حان الوقت لاختبار ما حصلنا عليه في تطبيق حقيقي.
من أجل معرفة تأثير التخلص من zone.js على أداء التطبيقات الزاوية ، استخدمت مشروع هوايتي
Cryptofolio .
قمت بتطبيق الديكور على جميع الروابط اللازمة المستخدمة في القوالب والمنطقة المعوقين. على سبيل المثال ، خذ بعين الاعتبار المكون التالي:
@Component({...}) export class AssetPricerComponent { @observed() price$: Observable<string>; @observed() trend$: Observable<Trend>;
يتم استخدام متغيرين في القالب:
price
(سيكون سعر الأصل موجودًا هنا)
trend
(يمكن لهذا المتغير رفع القيم ، التي
stale
down
، مما يشير إلى اتجاه تغير السعر). أنا زينت لهم مع
@observed
.
bund حجم حزمة المشروع
للبدء ، دعونا نلقي نظرة على مقدار انخفاض حجم حزمة المشروع أثناء التخلص من zone.js. أدناه هو نتيجة بناء المشروع مع zone.js.
نتيجة بناء مشروع مع zone.jsوهنا التجمع دون zone.js.
نتيجة بناء مشروع دون zone.jsانتبه إلى
polyfills-es2015.xxx.js
. إذا كان المشروع يستخدم zone.js ، فسيبلغ حجمه حوالي 35 كيلو بايت. ولكن بدون zone.js - 130 بايت فقط.
▍Nachalnaya تحميل
لقد بحثت خيارين التطبيق باستخدام المنارة. وترد نتائج هذه الدراسة أدناه. تجدر الإشارة إلى أنني لن آخذها على محمل الجد. الحقيقة هي أنه أثناء محاولة العثور على متوسط القيم ، حصلت على نتائج مختلفة بشكل كبير عن طريق إجراء العديد من القياسات لنفس إصدار التطبيق.
ربما يعتمد الاختلاف في تقييم خياري التطبيق فقط على حجم الحزم.
لذلك ، هنا هي النتيجة التي تم الحصول عليها لتطبيق يستخدم zone.js.
نتائج التحليل لتطبيق يستخدم zone.jsوهنا ما حدث بعد تحليل التطبيق الذي لم يتم استخدام zone.js.
نتائج التحليل لتطبيق لا يستخدم zone.js▍ الأداء
والآن وصلنا إلى الأكثر إثارة للاهتمام. هذا هو أداء تطبيق يعمل تحت الحمل. نريد أن نتعرف على شعور المعالج عندما يعرض التطبيق تحديثات أسعار لمئات الأصول عدة مرات في الثانية.
من أجل تحميل التطبيق ، أنشأت 100 كيانًا توفر بيانات شرطية بأسعار تتغير كل 250 مللي ثانية. إذا ارتفع السعر ، فسيتم عرضه باللون الأخضر. إذا خفضت - الأحمر. كل هذا يمكن أن يحمل جهاز MacBook Pro بجدية.
تجدر الإشارة إلى أنه أثناء العمل في القطاع المالي على العديد من التطبيقات المصممة لنقل أجزاء البيانات عالية التردد ، واجهت وضعا مماثلا عدة مرات.
لتحليل كيفية استخدام إصدارات مختلفة من التطبيق لموارد المعالج ، استخدمت أدوات مطوّري برامج Chrome.
إليك ما يبدو عليه التطبيق الذي يستخدم zone.js.
تحميل النظام الذي تم إنشاؤه بواسطة تطبيق يستخدم zone.jsوإليك كيفية عمل التطبيق في أي zone.js غير مستخدم.
تحميل النظام الذي تم إنشاؤه بواسطة تطبيق لا يستخدم zone.jsنقوم بتحليل هذه النتائج ، مع الانتباه إلى الرسم البياني لتحميل المعالج (الأصفر):
- كما ترون ، فإن التطبيق الذي يستخدم zone.js يقوم باستمرار بتحميل المعالج بنسبة 70-100 ٪! إذا كنت تبقي علامة تبويب المتصفح مفتوحة لفترة طويلة ، فإن إنشاء مثل هذا الحمل على النظام ، قد يفشل التطبيق الذي يتم تشغيله فيه.
- كما أن إصدار التطبيق حيث لا يتم استخدام zone.js ينشئ حملًا ثابتًا على المعالج في حدود 30 إلى 40٪. ! رائع
يرجى ملاحظة أنه تم الحصول على هذه النتائج مع فتح نافذة أدوات المطور في Chrome ، مما يضع ضغطًا على النظام ويبطئ التطبيق.
▍ زيادة الحمل
لقد حاولت التأكد من أن كل كيان مسؤول عن تحديث السعر سيصدر 4 تحديثات إضافية كل ثانية بالإضافة إلى ما ينتج بالفعل.
إليك ما تمكنا من معرفته حول التطبيق الذي لا يتم استخدام zone.js:
- تعامل هذا التطبيق عادة مع الحمل ، ويستخدم الآن حوالي 50 ٪ من موارد المعالج.
- لقد نجح في تحميل المعالج بقدر تحميل التطبيق مع zone.js ، فقط عندما يتم تحديث الأسعار كل 10 مللي ثانية (البيانات الجديدة ، كما كان من قبل ، جاءت من 100 كيان).
▍ تحليل الأداء مع Benchpress الزاوي
تحليل الأداء الذي أجريته أعلاه لا يمكن أن يسمى علميًا بشكل خاص. لدراسة أكثر جدية لأداء مختلف الأطر ، أوصي باستخدام
هذا المعيار . للبحث ، يجب على Angular اختيار الإصدار المعتاد من هذا الإطار وإصداره دون zone.js.
أنا ، مستوحاة من بعض الأفكار من هذا المعيار ، قمت بإنشاء
مشروع يقوم بحسابات ثقيلة. اختبرت أدائها مع
Angular Benchpress .
فيما يلي رمز المكون الذي تم اختباره:
@Component({...}) export class AppComponent { public data = []; @observed() run(length: number) { this.clear(); this.buildData(length); } @observed() append(length: number) { this.buildData(length); } @observed() removeAll() { this.clear(); } @observed() remove(item) { for (let i = 0, l = this.data.length; i < l; i++) { if (this.data[i].id === item.id) { this.data.splice(i, 1); break; } } } trackById(item) { return item.id; } private clear() { this.data = []; } private buildData(length: number) { const start = this.data.length; const end = start + length; for (let n = start; n <= end; n++) { this.data.push({ id: n, label: Math.random() }); } } }
أطلقت مجموعة صغيرة من المعايير باستخدام Protractor و Benchpress. تم تنفيذ العمليات عدد محدد من المرات.
مقاعد البدلاء في العملالنتائج
فيما يلي عينة من النتائج التي تم الحصول عليها باستخدام Benchpress.
نتائج مقاعد البدلاءفيما يلي شرح للمؤشرات الواردة في هذا الجدول:
gcAmount
: حجم عمليات gc (جمع القمامة) ، كيلو بايت.gcTime
: وقت تشغيل gc ، ms.majorGcTime
: وقت العمليات الرئيسية gc، ms.pureScriptTime
: وقت تنفيذ البرنامج النصي بالمللي ثانية ، باستثناء عمليات العرض والتقديم.renderTime
: وقت التقديم ، مللي ثانية.scriptTime
: وقت تنفيذ البرنامج النصي مع الأخذ في الاعتبار عمليات gc scriptTime
.
سننظر الآن في تحليل أداء بعض العمليات في أشكال التطبيق المختلفة. يعرض اللون الأخضر نتائج تطبيق يستخدم zone.js ، ويظهر اللون البرتقالي نتائج تطبيق دون zone.js. يرجى ملاحظة أنه يتم تحليل وقت العرض فقط هنا. إذا كنت مهتمًا بجميع نتائج الاختبار ، تحقق
هنا .
اختبار: إنشاء 1000 خطوط
في الاختبار الأول ، يتم إنشاء 1000 خطوط.
نتائج الاختباراختبار: إنشاء 10000 الصفوف
مع تزايد التحميل على التطبيقات ، يزداد الفرق في أدائها.
نتائج الاختباراختبار: الانضمام 1000 خطوط
في هذا الاختبار ، يتم إلحاق 1000 سطر بـ 10000 سطر.
نتائج الاختباراختبار: إزالة 10000 الصفوف
هنا ، يتم إنشاء 10000 سطر ، ثم يتم حذفها.
نتائج الاختبارTypeScript ديكور رمز المصدر
فيما يلي الكود المصدري لديكور TypeScript الذي تمت مناقشته هنا. يمكن العثور على هذا الرمز
هنا أيضًا.
النتائج
على الرغم من أنني آمل أن تكون قد أحببت قصتي حول تحسين أداء مشاريع Angular ، إلا أنني آمل ألا أميل إلى الاندفاع لإزالة zone.js من مشروعك. يجب أن تكون الاستراتيجية الموضحة هنا هي الملاذ الأخير الذي يمكنك اللجوء إليه من أجل زيادة أداء تطبيق Angular الخاص بك.
تحتاج أولاً إلى تجربة أساليب مثل استخدام استراتيجية الكشف عن تغيير OnPush ، تطبيق
trackBy
، تعطيل المكونات ، تنفيذ التعليمات البرمجية خارج zone.js ، أحداث zone.js القائمة السوداء (يمكن متابعة قائمة أساليب التحسين هذه). الطريقة الموضحة هنا مكلفة للغاية ، ولست متأكدًا من أن الجميع على استعداد لدفع هذا الثمن الباهظ للأداء.
في الواقع ، قد لا تكون التنمية دون zone.js الشيء الأكثر جاذبية. ربما هذا ليس فقط للشخص الذي يشارك في المشروع ، والذي يخضع لسيطرته الكاملة. هذا هو - هو صاحب التبعيات ولديه القدرة والوقت لجعل كل شيء في شكله الصحيح.
إذا تبين أنك جربت كل شيء وتعتقد أن عنق الزجاجة في مشروعك هو بالضبط zone.js ، فربما يجب عليك محاولة تسريع Angular بالكشف عن التغييرات بشكل مستقل.
آمل أن يكون هذا المقال قد سمح لك بمشاهدة ما تتوقعه Angular في المستقبل ، وما الذي تستطيع Ivy تحقيقه ، وما الذي يمكن أن تفعله zone.js لزيادة سرعة التطبيق.
أعزائي القراء! كيف يمكنك تحسين مشاريعك الزاوية التي تحتاج إلى أقصى قدر من الأداء؟
