تنفيذ ميزة رصد التغييرات بشكل أسرع لتحسين تجربة المستخدم
تنفّذ Angular آلية رصد التغييرات بشكلٍ دوري حتى تنعكس التغييرات في نموذج البيانات في طريقة عرض التطبيق. يمكن بدء عملية رصد التغيير إما يدويًا أو من خلال حدث غير متزامن (مثل تفاعل المستخدم أو إكمال XHR).
تُعدّ ميزة رصد التغييرات أداة فعّالة، ولكن إذا تم تشغيلها بشكل متكرّر جدًا، يمكن أن تؤدي إلى الكثير من العمليات الحسابية وحظر سلسلة التعليمات الرئيسية للمتصفّح.
في هذه المشاركة، ستتعرّف على كيفية التحكّم في آلية رصد التغييرات وتحسينها من خلال تخطّي أجزاء من تطبيقك وتشغيل ميزة رصد التغييرات عند الضرورة فقط.
داخل ميزة "رصد التغييرات" في Angular
لفهم طريقة عمل ميزة "رصد التغيير" في Angular، لنلقِ نظرة على نموذج تطبيق.
يمكنك العثور على الرمز البرمجي للتطبيق في مستودع GitHub هذا.
يعرض التطبيق قائمة بالموظفين من قسمَين في إحدى الشركات، وهما المبيعات والبحث والتطوير، ويتضمّن عنصرَين:
AppComponent
، وهو المكوّن الأساسي للتطبيق- مثالان على
EmployeeListComponent
، أحدهما للمبيعات والآخر للبحث والتطوير
يمكنك الاطّلاع على مثيلَي EmployeeListComponent
في النموذج الخاص بـ AppComponent
:
<app-employee-list
[data]="salesList"
department="Sales"
(add)="add(salesList, $event)"
(remove)="remove(salesList, $event)"
></app-employee-list>
<app-employee-list
[data]="rndList"
department="R&D"
(add)="add(rndList, $event)"
(remove)="remove(rndList, $event)"
></app-employee-list>
لكل موظف اسم وقيمة رقمية. ينقل التطبيق القيمة الرقمية للموظف إلى عملية حسابية تجارية ويعرض النتيجة على الشاشة.
إليك نظرة على EmployeeListComponent
:
const fibonacci = (num: number): number => {
if (num === 1 || num === 2) {
return 1;
}
return fibonacci(num - 1) + fibonacci(num - 2);
};
@Component(...)
export class EmployeeListComponent {
@Input() data: EmployeeData[];
@Input() department: string;
@Output() remove = new EventEmitter<EmployeeData>();
@Output() add = new EventEmitter<string>();
label: string;
handleKey(event: any) {
if (event.keyCode === 13) {
this.add.emit(this.label);
this.label = '';
}
}
calculate(num: number) {
return fibonacci(num);
}
}
تقبل الدالة EmployeeListComponent
قائمة بالموظفين واسم القسم كمدخلات. عندما يحاول المستخدم إزالة موظف أو إضافته، يشغّل المكوّن ناتجًا مطابقًا. يحدّد المكوّن أيضًا الطريقة calculate
التي تنفّذ عملية الحساب التجاري.
في ما يلي نموذج EmployeeListComponent
:
<h1 title="Department">{{ department }}</h1>
<mat-form-field>
<input placeholder="Enter name here" matInput type="text" [(ngModel)]="label" (keydown)="handleKey($event)">
</mat-form-field>
<mat-list>
<mat-list-item *ngFor="let item of data">
<h3 matLine title="Name">
{{ item.label }}
</h3>
<md-chip title="Score" class="mat-chip mat-primary mat-chip-selected" color="primary" selected="true">
{{ calculate(item.num) }}
</md-chip>
</mat-list-item>
</mat-list>
يتكرّر هذا الرمز على جميع الموظفين في القائمة، ويعرض عنصر قائمة لكل موظف. يتضمّن أيضًا توجيه ngModel
لربط البيانات في اتجاهَين بين الإدخال والسمة label
المعرَّفة في EmployeeListComponent
.
باستخدام مثيلَي EmployeeListComponent
، يشكّل التطبيق شجرة المكوّنات التالية:
AppComponent
هو المكوّن الأساسي للتطبيق. مكوّناتها الفرعية هي مثيلان من EmployeeListComponent
. تحتوي كل نسخة على قائمة عناصر (E1 وE2 وما إلى ذلك) تمثّل الموظفين الفرديين في القسم.
عندما يبدأ المستخدم في إدخال اسم موظف جديد في مربّع الإدخال في EmployeeListComponent
، تبدأ Angular في رصد التغيير لشجرة المكوّنات بأكملها بدءًا من AppComponent
. وهذا يعني أنّه أثناء كتابة المستخدم في حقل إدخال النص، يعيد Angular احتساب القيم الرقمية المرتبطة بكل موظف بشكل متكرر للتحقّق من عدم تغيُّرها منذ آخر عملية تحقّق.
لمعرفة مدى بطء هذه العملية، افتح النسخة غير المحسّنة من المشروع على StackBlitz وحاوِل إدخال اسم موظف.
يمكنك التأكّد من أنّ التباطؤ ناتج عن الدالة fibonacci
من خلال إعداد المشروع النموذجي وفتح علامة التبويب الأداء في "أدوات مطوّري البرامج في Chrome".
- اضغط على Control+Shift+J (أو Command+Option+J على أجهزة Mac) لفتح "أدوات مطوّلي البرامج".
- انقر على علامة التبويب الأداء.
انقر الآن على تسجيل (في أعلى يمين لوحة الأداء) وابدأ الكتابة في أحد مربّعات النص في التطبيق. بعد بضع ثوانٍ، انقر على تسجيل مرة أخرى لإيقاف التسجيل. بعد أن تعالج "أدوات مطوّري البرامج في Chrome" جميع بيانات إنشاء الملفات الشخصية التي جمعتها، سيظهر لك ما يلي:
إذا كان هناك العديد من الموظفين في القائمة، قد تحظر هذه العملية سلسلة محادثات واجهة المستخدم في المتصفّح وتتسبّب في فقدان بعض اللقطات، ما يؤدي إلى تجربة سيئة للمستخدم.
تخطّي الأشجار الفرعية للمكوّنات
عندما يكتب المستخدم في حقل إدخال النص الخاص بالمبيعات EmployeeListComponent
، أنت تعلم أنّ البيانات في قسم البحث والتطوير لا تتغير، لذا ليس هناك سبب لتشغيل ميزة رصد التغييرات على مكوناته. للتأكّد من أنّ مثيل البحث والتطوير لا يؤدي إلى رصد التغيير، اضبط changeDetectionStrategy
الخاص بـ EmployeeListComponent
على OnPush
:
import { ChangeDetectionStrategy, ... } from '@angular/core';
@Component({
selector: 'app-employee-list',
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['employee-list.component.css']
})
export class EmployeeListComponent {...}
عندما يكتب المستخدم في حقل إدخال نص، لن يتم تفعيل ميزة رصد التغيير إلا للقسم المعنيّ:
يمكنك الاطّلاع على هذا التحسين في التطبيق الأصلي هنا.
يمكنك الاطّلاع على مزيد من المعلومات حول استراتيجية رصد التغييرات OnPush
في مستندات Angular الرسمية.
لمعرفة تأثير هذا التحسين، أدخِل موظفًا جديدًا في التطبيق على StackBlitz.
استخدام الأنابيب النقية
على الرغم من أنّ استراتيجية رصد التغيير في EmployeeListComponent
تم ضبطها الآن على OnPush
، لا يزال Angular يعيد احتساب القيمة الرقمية لجميع الموظفين في قسم ما عندما يُدخل المستخدم النص المقابل.
لتحسين هذا السلوك، يمكنك الاستفادة من عمليات النقل المباشر. تقبل كل من الأنابيب النقية وغير النقية المدخلات وتعرض النتائج التي يمكن استخدامها في نموذج. الفرق بينهما هو أنّ الأداة النقية تعيد احتساب النتيجة فقط إذا تلقّت إدخالاً مختلفًا عن الاستدعاء السابق.
تذكَّر أنّ التطبيق يحتسب قيمة لعرضها استنادًا إلى القيمة الرقمية للموظف، وذلك من خلال استدعاء الطريقة calculate
المحدّدة في EmployeeListComponent
. إذا نقلت عملية الاحتساب إلى قناة خالصة، سيعيد Angular احتساب تعبير القناة فقط عند تغيير وسيطاته. سيحدّد إطار العمل ما إذا كانت وسيطات الأداة قد تغيّرت من خلال إجراء عملية تحقّق من المرجع. وهذا يعني أنّ Angular لن يعيد إجراء أي عمليات إعادة احتساب ما لم يتم تعديل القيمة الرقمية للموظف.
في ما يلي كيفية نقل عملية حساب النشاط التجاري إلى قناة باسم CalculatePipe
:
import { Pipe, PipeTransform } from '@angular/core';
const fibonacci = (num: number): number => {
if (num === 1 || num === 2) {
return 1;
}
return fibonacci(num - 1) + fibonacci(num - 2);
};
@Pipe({
name: 'calculate'
})
export class CalculatePipe implements PipeTransform {
transform(val: number) {
return fibonacci(val);
}
}
تستدعي الطريقة transform
في الأنبوب الدالة fibonacci
. لاحظ أنّ الممرّ نقي. ستعتبر Angular جميع الأنابيب نقية ما لم تحدّد خلاف ذلك.
أخيرًا، عدِّل التعبير داخل النموذج الخاص بـ EmployeeListComponent
:
<mat-chip-list>
<md-chip>
{{ item.num | calculate }}
</md-chip>
</mat-chip-list>
هذا كل شيء! الآن، عندما يكتب المستخدم في حقل إدخال النص المرتبط بأي قسم، لن يعيد التطبيق احتساب القيمة الرقمية للموظفين الفرديين.
في التطبيق أدناه، يمكنك ملاحظة مدى سلاسة الكتابة.
للاطّلاع على تأثير آخر عملية تحسين، جرِّب هذا المثال على StackBlitz.
يتوفّر الرمز البرمجي مع تحسين الأنابيب النقية للتطبيق الأصلي هنا.
الخاتمة
عند مواجهة بطء في وقت التشغيل في تطبيق Angular، اتّبِع الخطوات التالية:
- يمكنك تحديد المشاكل في التطبيق باستخدام "أدوات مطوّري البرامج في Chrome" لمعرفة مصدر بطء الأداء.
- قدِّم استراتيجية
OnPush
لرصد التغييرات من أجل حذف الأجزاء الفرعية من شجرة المكوّن. - انقل العمليات الحسابية المعقّدة إلى أنابيب خالصة للسماح للإطار بتخزين القيم المحسوبة مؤقتًا.