جافاسكريبت - متى يقوم ChangeDetectionStrategy.OnPush فعليًا بتشغيل كشف التغييرات؟
أنا أبني تطبيق Angular 4 يحتوي على العديد من المكونات حيث تكون ChangeDetectionStrategy هي OnPush. بينما الوثائق الرسمية لـ Angular حول هذا الموضوع تفتقر إلى الكثير من المعلومات، تقول مصادر مختلفة إن مكونات OnPush تتحدث فقط عندما تتغير @Input الخاصة بها (كائنات جديدة أو أنواع بدائية).
ومع ذلك، يبدو أن الأحداث المختلفة داخل مكوّن OnPush ستؤدي أيضًا إلى تشغيل كشف التغييرات. ومع ذلك، ألاحظ أن أحداثًا أخرى لا تؤدي إلى تشغيل كشف التغييرات.
ما هي القواعد المحددة لـ ChangeDetectionStrategy.OnPush فيما يتعلق بالأحداث داخل المكوّن؟
الإجابة رقم 1
تحتوي هذه التدوينة من جامعة Angular على عدة مؤشرات حول الأحداث التي تُحفز كشف التغييرات عند استخدام ChangeDetectionStrategy.OnPush:
يتم تفعيل كاشف التغير OnPush في بعض الحالات الأخرى بالإضافة إلى التغيرات في مراجع Input() للمكون، كما يتم تفعيله على سبيل المثال:
- إذا تم تشغيل معالج حدث المكوّن
- إذا أصدر كائن مرصود مربوط بالقالب عبر أنبوب الـ async قيمة جديدة
وأضافوا التوصيات التالية:
لذا إذا تذكرنا الاشتراك في أي متغيرات قابلة للملاحظة قدر الإمكان باستخدام أنبوب async على مستوى القالب، نحصل على عدة مزايا:
- سنواجه مشكلة اكتشاف التغيير أقل بكثير عند استخدام OnPush
- سنجعل من الأسهل بكثير التحويل من استراتيجية كشف التغيير الافتراضية إلى OnPush لاحقًا إذا احتجنا إلى ذلك
- البيانات غير القابلة للتغيير ومقارنة المرجع بـ @Input() ليست الطريقة الوحيدة لتحقيق واجهة مستخدم ذات أداء عالٍ مع OnPush: النهج التفاعلي هو أيضًا خيار لاستخدام OnPush بشكل فعال
في تعليق تابع للسؤال "كشف التغيير في Angular 2" على Disqus، يشرح فيكتور سافكين:
عند استخدام كاشفات OnPush، فإن الإطار سيتحقق من مكون OnPush عندما تتغير أي من خصائص الإدخال الخاصة به، أو عندما يتم تشغيل حدث، أو عندما يقوم كائن قابل للملاحظة بتشغيل حدث.
الإجابة رقم 2
من استراتيجية اكتشاف التغيير: OnPush،
سيُعلم هذا Angular بأن مكوننا يعتمد فقط على مدخلاته وأن أي كائن يُمرر إليه يجب اعتباره غير قابل للتغيير.
يتابع ليظهر أن تغييرات الخصائص لا تُحدث كشف التغيرات (تُعتبر ثابتة)، لكن التغييرات في الكائن نفسه تُحدث كشف التغيرات.
يبدو أن المعيار الرئيسي هو الثبات. المحفزات للتغيير لكائن يتم الرجوع إليه في القالب هي،
- تغيير مرجع الكائن في الكود (سواء قام الأصل بتغيير قيمة الإدخال أو التغيير داخل الطفل) سيؤدي إلى تفعيل اكتشاف التغيير.
- التغيرات في خاصية الكائن من خارج المكون لن تؤدي إلى تفعيل الكشف عن التغييرات.
- تغييرات خاصية الكائن من داخل المكون ستؤدي إلى اكتشاف التغيرات.
لم أختبر تغييرًا من كائن يمكن ملاحظته، لكن ذلك يُعطى في مكان آخر كمُحَفِّز. (إصدار كائن يمكن ملاحظته يُغيّر مرجع الكائن، وليس خصائصه).
تم الاختبار مع إصدار أنغولار 5.2.0-beta.1
توسيع مثال RangleIO
صفحة RangleIO المشار إليها في أعلى المنشور تحتوي على Plunker يوضح أن الكائنات التي يتم تمريرها إلى مكون عبر معلمات Input() تعتبر غير قابلة للتغيير. كاشف التغييرات في المكون الفرعي لن يستجيب لتغييرات خصائص كائن الإدخال.
ومع ذلك، بعد تكرار أزرار التغيير داخل مكون الفيلم، يمكن ملاحظة أن تغييرات خصائص الكائن المستدعاة داخل كود المكون نفسه ستغير عرضه.
إليك الكود، بلانكر
movie.component.ts
@Component({
selector: 'app-movie',
styles: ['div {border: 1px solid black}'],
template: `
<div>
<h3>{{ title }}</h3>
<p>
<label>Actor:</label>
<span>{{actor.firstName}} {{actor.lastName}}</span>
</p>
<button type="button" (click)="changeActorProperties()">
Inside Movie Component - Change Actor Properties (will change view)
</button>
<button type="button" (click)="changeActorObject()">
Inside Movie Component - Change Actor Object (will change view)
</button>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MovieComponent {
@Input() title: string;
@Input() actor: Actor;
changeActorProperties(): void {
this.actor.firstName = 'Nicholas';
this.actor.lastName = 'Cage';
}
changeActorObject(): void {
this.actor = new Actor('Bruce', 'Willis');
}
}
app.component.ts
@Component({
selector: 'app-root',
template: `
<h1>MovieApp</h1>
<p>{{ slogan }}</p>
<button type="button" (click)="changeActorProperties()">
Outside Movie Component - Change Actor Properties (will not change Movie view)
</button>
<button type="button" (click)="changeActorObject()">
Outside Movie Component - Change Actor Object (will change Movie view)
</button>
<app-movie [title]="title" [actor]="actor"></app-movie>`
})
export class AppComponent {
slogan = 'Just movie information';
title = 'Terminator 1';
actor = new Actor('Arnold', 'Schwarzenegger');
changeActorProperties(): void {
this.actor.firstName = 'Nicholas';
this.actor.lastName = 'Cage';
}
changeActorObject(): void {
this.actor = new Actor('Bruce', 'Willis');
}
}
