كيفية تحسين أداء تطبيق Angular الخاص بك

نشرت: 2020-04-10

عند الحديث عن أعظم أطر عمل للواجهة الأمامية ، من المستحيل عدم ذكر Angular. يتطلب الأمر الكثير من الجهد من المبرمجين لتعلمه واستخدامه بحكمة. لسوء الحظ ، هناك خطر يتمثل في أن المطورين غير المتمرسين في Angular يمكنهم استخدام بعض ميزاته بطريقة غير فعالة.

أحد الأشياء العديدة التي تحتاج دائمًا إلى العمل عليها كمطور للواجهة الأمامية هو أداء التطبيق. ركز جزء كبير من مشاريعي السابقة على تطبيقات المؤسسات الكبيرة التي يستمر توسيعها وتطويرها. ستكون أطر الواجهة الأمامية مفيدة للغاية هنا ، ولكن من المهم استخدامها بشكل صحيح ومعقول.

لقد أعددت قائمة سريعة لاستراتيجيات ونصائح تعزيز الأداء الأكثر شيوعًا والتي قد تساعدك على زيادة أداء تطبيق Angular الخاص بك على الفور. يرجى أن تضع في اعتبارك أن جميع التلميحات هنا تنطبق على Angular في الإصدار 8.

ChangeDetectionStrategy و ChangeDetectorRef

كشف التغيير (CD) هو آلية Angular لاكتشاف تغييرات البيانات والرد عليها تلقائيًا. يمكننا سرد النوع الأساسي لتغييرات حالة التطبيق القياسية:

  • الأحداث
  • طلب HTTP
  • الموقتات

هذه تفاعلات غير متزامنة. السؤال هو: كيف يعرف Angular أن بعض التفاعلات (مثل النقر ، الفاصل الزمني ، طلب http) قد حدثت وأن هناك حاجة لتحديث حالة التطبيق؟

الإجابة هي ngZone ، وهو نظام معقد يهدف إلى تتبع التفاعلات غير المتزامنة. إذا تم تسجيل جميع العمليات بواسطة ngZone ، فإن Angular يعرف متى يتفاعل مع بعض التغييرات. لكنها لا تعرف ما الذي تغير بالضبط وتطلق آلية كشف التغيير ، التي تتحقق من جميع المكونات بترتيب العمق الأول.

يحتوي كل مكون في تطبيق Angular على كاشف التغيير الخاص به ، والذي يحدد كيفية عمل هذا المكون عند إطلاق اكتشاف التغيير - على سبيل المثال ، إذا كانت هناك حاجة لإعادة تقديم DOM الخاص بالمكون (وهي عملية مكلفة إلى حد ما). عندما يقوم Angular بتشغيل Change Detection ، سيتم فحص كل مكون ويمكن إعادة عرضه (DOM) افتراضيًا.

يمكننا تجنب ذلك باستخدام ChangeDetectionStrategy.OnPush:

 @مكون({
  المحدد: "foobar" ،
  templateUrl: "./foobar.component.html" ،
  styleUrls: ['./foobar.component.scss']،
  changeDetection: ChangeDetectionStrategy.OnPush
})

كما ترون في مثال الكود أعلاه ، علينا إضافة معلمة إضافية لمصمم المكون. ولكن كيف تعمل إستراتيجية الكشف عن التغيير الجديدة هذه حقًا؟

تخبر الإستراتيجية Angular أن مكونًا معينًا يعتمد فقط على Inputs (). أيضًا ، ستعمل جميع المكوناتInputs () ككائن غير قابل للتغيير (على سبيل المثال ، عندما نغير الخاصية في كائنInput () ، بدون تغيير المرجع ، لن يتم التحقق من هذا المكون). هذا يعني أنه سيتم حذف الكثير من عمليات الفحص غير الضرورية ويجب أن يؤدي ذلك إلى زيادة أداء تطبيقنا.

سيتم التحقق من مكون مع ChangeDetectionStrategy.OnPush فقط في الحالات التالية:

  • Input () سيتغير مرجع
  • سيتم تشغيل حدث في قالب المكون أو أحد توابعه
  • يمكن ملاحظته في المكون سيؤدي إلى تشغيل حدث
  • سيتم تشغيل القرص المضغوط يدويًا باستخدام خدمة ChangeDetectorRef
  • يتم استخدام الأنبوب غير المتزامن في العرض (يشير الأنبوب غير المتزامن إلى المكون المراد فحصه من أجل التغييرات - عندما يصدر تيار المصدر قيمة جديدة سيتم التحقق من هذا المكون)

إذا لم يحدث أي مما سبق ، فإن استخدام ChangeDetectionStrategy.OnPush داخل مكون معين يؤدي إلى عدم التحقق من المكون وجميع المكونات المتداخلة بعد بدء تشغيل القرص المضغوط.

لحسن الحظ ، لا يزال بإمكاننا التحكم الكامل في الاستجابة لتغييرات البيانات باستخدام خدمة ChangeDetectorRef. علينا أن نتذكر أنه مع ChangeDetectionStrategy.OnPush داخل المهلات والطلبات واسترجاع الاشتراكات ، نحتاج إلى تشغيل القرص المضغوط يدويًا إذا كنا بحاجة فعلاً إلى هذا:

 عداد = 0 ؛

المُنشئ (تغيير خاص: ChangeDetectorRef) {}

ngOnInit () {
  setTimeout (() => {
    this.counter + = 1000 ؛
    this.changeDetectorRef.detectChanges () ،
  } ، 1000) ؛
}

كما نرى أعلاه ، من خلال استدعاء this.changeDetectorRef.detectChanges () داخل وظيفة المهلة ، يمكننا فرض القرص المضغوط يدويًا. إذا تم استخدام العداد داخل القالب بأي طريقة ، فسيتم تحديث قيمته.

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

 this.changeDetectorRef.detach ()

يجب تنفيذ هذا الرمز داخل أسلوب دورة حياة ngAfterViewInit () أو ngAfterViewChecked () للتأكد من عرض طريقة العرض بشكل صحيح قبل تعطيل تحديث البيانات. لن يتم التحقق من هذا المكون أثناء القرص المضغوط ، إلا إذا قمنا بتشغيل DiscoverChanges () يدويًا.

استدعاءات الوظائف وحساباتها في النموذج

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

أنابيب نقية

الأنابيب النقية هي نوع من الأنابيب ذات خرج يعتمد فقط على مدخلاتها ، دون أي آثار جانبية. لحسن الحظ ، جميع الأنابيب في Angular نقية بشكل افتراضي.

 @يضخ({
    الاسم: "أحرف كبيرة" ،
    نقية حقيقية
})

ولكن لماذا يجب أن نتجنب استخدام الأنابيب مع Pure: false؟ الجواب هو تغيير الكشف مرة أخرى. يتم تنفيذ الأنابيب غير النقية في كل تشغيل قرص مضغوط ، وهو أمر غير ضروري في معظم الحالات ويؤدي إلى انخفاض أداء تطبيقنا. فيما يلي مثال على الوظيفة التي يمكننا تغييرها إلى أنبوب نقي:

 تحويل (القيمة: سلسلة ، الحد = 60 ، علامة القطع = '...') {
  إذا كانت (! value || value.length <= limit) {
    قيمة الإرجاع؛
  }
  const numberOfVisibleCharacters = value.substr (0، limit) .lastIndexOf ('') ؛
  إرجاع `$ {value.substr (0، numberOfVisibleCharacters)} $ {ellipsis}`؛
}

ودعونا نرى المنظر:

 <p class = "description"> اقتطاع (نص ، 30) </p>

يمثل الرمز أعلاه الوظيفة الصرفة - لا توجد آثار جانبية ، يعتمد الإخراج فقط على المدخلات. في هذه الحالة ، يمكننا ببساطة استبدال هذه الوظيفة بأنبوب نقي :

 @يضخ({
  الاسم: "اقتطاع" ،
  نقية حقيقية
})
فئة التصدير TruncatePipe تنفذ PipeTransform {
  تحويل (القيمة: سلسلة ، الحد = 60 ، علامة القطع = '...') {
    ...
  }
}

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

 <p class = "description"> {{text | اقتطاع: 30}} </p>

وحدات التحميل والتحميل المسبق الكسولة

عندما يحتوي التطبيق الخاص بك على أكثر من صفحة واحدة ، يجب أن تفكر بالتأكيد في إنشاء وحدات لكل جزء منطقي من مشروعك ، وخاصة وحدات التحميل البطيئة . لنفكر في رمز جهاز التوجيه الزاوي البسيط:

 مسارات const: المسارات = [
  {
    طريق: ''،
    المكون: HomeComponent
  } ،
  {
    المسار: "foo" ،
    loadChildren: () => import ("./ foo / foo.module"). ثم (m => m.FooModule)
  } ،
  {
    المسار: "شريط" ،
    loadChildren: () => import ("./ bar / bar.module"). ثم (m => m.BarModule)
  }
]
NgModule ({
  الصادرات: [RouterModule] ،
  الواردات: [RouterModule.forRoot (المسارات)]
})
فئة AppRoutingModule {}

في المثال أعلاه ، يمكننا أن نرى أنه سيتم تحميل fooModule بكل أصوله فقط عندما يحاول المستخدم إدخال مسار معين (foo أو bar). ستنشئ Angular أيضًا قطعة منفصلة لهذه الوحدة . التحميل الكسول سيقلل من الحمل الأولي .

يمكننا القيام ببعض التحسين الإضافي. لنفترض أننا نريد أن نجعل وحدات تحميل التطبيق الخاصة بنا في الخلفية. في هذه الحالة ، يمكننا استخدام إستراتيجية التحميل المسبق. يحتوي Angular افتراضيًا على نوعين من الإستراتيجية للتحميل المسبق:

  • لا
  • PreloadAllModules

في الكود أعلاه ، يتم استخدام إستراتيجية NoPreloading افتراضيًا. يبدأ التطبيق في تحميل وحدة معينة بناءً على طلب المستخدم (عندما يريد المستخدم رؤية مسار معين). يمكننا تغيير هذا عن طريق إضافة بعض التهيئة الإضافية إلى جهاز التوجيه.

 NgModule ({
  الصادرات: [RouterModule] ،
  الواردات: [RouterModule.forRoot (المسارات ، {
       الإستراتيجية: PreloadAllModules
  }]
})
فئة AppRoutingModule {}

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

لنفترض أننا نريد تحميل الوحدات المحددة فقط مسبقًا ، على سبيل المثال ، BarModule. نشير إلى ذلك عن طريق إضافة حقل إضافي لحقل البيانات.

 مسارات const: المسارات = [
  {
    طريق: ''،
    المكون: HomeComponent
    البيانات: {preload: false}
  } ،
  {
    المسار: "foo" ،
    loadChildren: () => import ("./ foo / foo.module"). ثم (m => m.FooModule) ،
    البيانات: {preload: false}
  } ،
  {
    المسار: "شريط" ،
    loadChildren: () => import ("./ bar / bar.module"). ثم (m => m.BarModule) ،
    البيانات: {preload: true}
  }
]

ثم يتعين علينا كتابة وظيفة التحميل المسبق المخصصة لدينا:

 @ حقن ()
فئة التصدير CustomPreloadingStrategy تنفذ PreloadingStrategy {
  التحميل المسبق (المسار: المسار ، التحميل: () => يمكن ملاحظته <أي>): يمكن ملاحظته <أي> {
    عودة route.data && route.data.preload؟ تحميل (): من (خالية) ؛
  }
}

وقم بتعيينها كاستراتيجية للتحميل المسبق:

 NgModule ({
  الصادرات: [RouterModule] ،
  الواردات: [RouterModule.forRoot (المسارات ، {
       الإستراتيجية: CustomPreloadingStrategy
  }]
})
فئة AppRoutingModule {}

في الوقت الحالي ، سيتم تحميل المسارات التي تحتوي على معلمة {data: {preload: true}} فقط. ستعمل بقية المسارات مثل تعيين NoPreloading.

Custom preloadingStrategy هيInjectable () ، وهذا يعني أنه يمكننا حقن بعض الخدمات في الداخل إذا احتجنا إلى وتخصيص إستراتيجية التحميل المسبق الخاصة بنا بأي طريقة أخرى.
باستخدام أدوات مطور المتصفح ، يمكننا التحقيق في تعزيز الأداء عن طريق تساوي وقت التحميل الأولي مع إستراتيجية التحميل المسبق وبدونها. يمكننا أيضًا إلقاء نظرة على علامة تبويب الشبكة لمعرفة أنه يتم تحميل أجزاء المسارات الأخرى في الخلفية ، بينما يكون المستخدم قادرًا على رؤية الصفحة الحالية دون أي تأخير.

تتبع بواسطة الوظيفة

يمكننا أن نفترض أن معظم تطبيقات Angular تستخدم * ngFor للتكرار على العناصر المدرجة داخل القالب. إذا كانت القائمة المتكررة قابلة للتحرير أيضًا ، فإن trackBy أمر لا بد منه تمامًا.

 <ul>
  <tr * ngFor = "let product of products؛ trackBy: trackByProductId">
    <td> {{product.title}} </td>
  </tr>
</ul>

trackByProductId (الفهرس: الرقم ، المنتج: المنتج) {
  إرجاع المنتج.
}

باستخدام دالة trackBy ، يمكن لـ Angular تتبع عناصر المجموعات التي تغيرت (بواسطة معرف معين) وإعادة عرض هذه العناصر المحددة فقط. عندما نحذف trackBy ، ستتم إعادة تحميل القائمة بأكملها والتي يمكن أن تكون عملية كثيفة الاستخدام للموارد على DOM.

تجميع قبل الوقت (AOT)

فيما يتعلق بالتوثيق الزاوي:

" (...) المكونات والقوالب التي يوفرها Angular لا يمكن فهمها بواسطة المتصفح مباشرة ، تتطلب تطبيقات Angular عملية تجميع قبل أن تتمكن من تشغيلها في المتصفح "

يوفر Angular نوعي التجميع:

  • Just-in-Time (JIT) - يجمع تطبيقًا في المتصفح في وقت التشغيل
  • Ahead-of-Time (AOT) - يجمع تطبيقًا في وقت الإنشاء

لاستخدام التطوير ، يجب أن يغطي تجميع JIT احتياجات المطور. ومع ذلك ، لبناء الإنتاج ، يجب علينا بالتأكيد استخدام AOT . نحتاج إلى التأكد من ضبط علامة aot داخل ملف angular.json على true. تشمل أهم فوائد هذا الحل العرض الأسرع وعدد الطلبات غير المتزامنة الأقل وحجم تنزيل إطار العمل الأصغر وزيادة الأمان.

ملخص

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

تحرير رمز المنتج

ثق في M Liquido بمشروعك Angular

اتصل بنا

هل تريد تطوير تطبيق باستخدام M Liquido؟

هل تفكر في تعزيز عملك باستخدام تطبيق Angular؟ تواصل معنا واختر خدمات تطوير تطبيقات Angular الخاصة بنا.