بهینه سازی تشخیص تغییر زاویه ای با OnPush: پرش از زیر درختان برای عملکرد

استراتژی OnPush Master Angular برای ساخت برنامه های سریعتر و کارآمدتر. بیاموزید که چگونه از چک های غیر ضروری پرش کنید ، از سیگنال ها برای واکنش پذیری استفاده کنید و بهترین روشهای دنیای واقعی را با حداقل دیگ بخار اعمال کنید.
در ابتدا منتشر شده در: anastasios.theodosiou.me
tl ؛ دکتر
- تشخیص پیش فرض Angular هر مؤلفه را در هر چرخه بررسی می کند ، که می تواند در برنامه های بزرگ ناکارآمد باشد.
- در
OnPush
استراتژی با پرش از زیر درختان عملکرد را بهبود می بخشد ، مگر اینکه برخی از محرک ها (مانند ورودی های جدید یا رویدادها) رخ دهند. - سیگنال ها (معرفی شده در زاویه 16 و بهبود یافته در زاویه 17+) بدوی واکنشی هستند که یکپارچه با OnPush کار می کنند.
- با استفاده از
signal()
باinput()
، وasync
لوله اجزای بسیار عملکردی و واکنشی را با حداقل دیگ بخار امکان پذیر می کند. - در این مقاله به نمونه های کد عملی ، مشکلات رایج و بهترین روشها برای ترکیب مؤثر OnPush و سیگنال ها می پردازد.
مقدمه
برای توسعه دهندگان زاویه ای با هدف تقویت عملکرد و کاهش چک های غیر ضروری UI ، تسلط بر استراتژی تشخیص تغییر OnPush ضروری است. این مقاله شما را از طریق نحوه عملکرد ، چرا اهمیت دارد و چگونه می توان از آن به طور مؤثر استفاده کرد.
تشخیص تغییر Angular مکانیسمی است که UI را با داده ها همگام نگه می دارد. به طور پیش فرض ، Angular از پیش فرض (یا “checkalways”) استراتژی تشخیص را تغییر دهید ، این بدان معناست که هر بار که چرخه تشخیص تغییر اجرا می شود (به عنوان مثال بعد از یک رویداد کاربر ، تایمر ، پاسخ HTTP و غیره) ، Angular Traverse خواهد شد کل درخت مؤلفه از ریشه ، بررسی اتصالات الگوی هر مؤلفه برای تغییرات. این استراتژی پیش فرض ساده است و تضمین می کند که همه تغییرات گرفتار شده اند ، اما می تواند در برنامه های بزرگ ناکارآمد شود زیرا هر مؤلفه در هر چرخه بررسی می شود، حتی اگر بیشتر آنها تغییر نکرده اند (آخرین راهنمای تشخیص تغییر زاویه ای که به آن احتیاج دارید – مایکل هافمن | مایکل هافمن).
برای بهبود عملکرد ، Angular فراهم می کند اپشکر استراتژی تشخیص را تغییر دهید. استراتژی onPush به زاویه اجازه می دهد از زیر درختان پرش کنید اگر می داند که این مؤلفه ها تغییر نکرده اند ، از درخت مؤلفه در هنگام تشخیص تغییر. به عبارت دیگر ، با استفاده از OnPush ، Angular همیشه یک مؤلفه و فرزندان آن را بررسی نمی کند-از بررسی آن زیر درخت “امتناع می کند” مگر اینکه شرایط خاصی برآورده شود. این می تواند کار انجام شده در هر چرخه تشخیص تغییر را تا حد زیادی کاهش دهد و کاربرد شما کارآمدتر شود.
onpush چگونه کار می کند؟ یک مؤلفه تنظیم شده ChangeDetectionStrategy.OnPush
به زاویه ای می گوید: “اگر دلیل خاصی دارید فقط مرا برای تغییرات بررسی کنید.” این دلایل شامل:
-
مرجع ورودی جدید: مؤلفه مقادیر جدید input () را دریافت می کند (زاویه ای مقدار جدید را با مقدار قدیمی با استفاده از آن مقایسه می کند
===
برابری) (پرش از زیر درختان اجزای • زاویه ای). اگر مرجع ورودی تغییر کرده باشد (یا مقدار بدوی تغییر کرده است) ، Angular این مؤلفه onPush را به عنوان نیاز به بررسی نشان می دهد. - یک رویداد در مؤلفه یا فرزندان آن: هر رویداد کاربر یا خروجی ساطع شده در داخل این مؤلفه (یا هر یک از مؤلفه های کودک آن) مؤلفه ای را برای بررسی (Skipping Component Subtrees • Angular) نشان می دهد. به عنوان مثال ، یک رویداد کلیک بر روی یکی از دکمه های آن باعث می شود که مؤلفه OnPush تشخیص تغییر را برای خودش و زیر درخت آن اجرا کند.
-
ماشه دستی: تماس با مؤلفه
ChangeDetectorRef
روش ها ، مانندmarkForCheck()
یاdetectChanges()
، می تواند به صراحت مؤلفه را برای بررسی یا شروع چک نشان دهد. -
انتشار لوله های Async: اگر الگوی از آن استفاده کند
async
لوله برای عضویت در یک وعده/وعده مشاهده ، انتشار مقدار جدید مؤلفه ای را برای بررسی به صورت خودکار نشان می دهد (لوله Async در داخل تماس می گیردmarkForCheck
هنگامی که یک مقدار جدید وارد می شود (زاویه ای: واکنش پذیری آزمون با استراتژی OnPush | حاشیه لاکلاکو)).
اگر هیچ یک از این شرایط رخ ندهد ، یک جزء OnPush (و فرزندان آن) در هنگام تشخیص تغییر به سادگی از بین می رود. در مقابل ، استراتژی پیش فرض (استفاده می شود مگر اینکه در غیر این صورت مشخص شود) هر بار بدون در نظر گرفتن مؤلفه را بررسی می کند. این باعث می شود OnPush ابزاری قدرتمند برای بهینه سازی عملکرد توسط پرش از چک های غیر ضروری وقتی می دانید که به روزرسانی داده های یک مؤلفه محدود به محرک های خاص است.
در بخش های بعدی ، ما عمیق تر به این موضوع خواهیم رسید که چگونه تشخیص تغییر در پیش فرض در مقابل ONPush کار می کند ، سناریوهای مشترک را با OnPush بررسی می کنیم و به نمونه های کد ، بهترین شیوه ها و مشکلات هنگام استفاده از OnPush برای جستجوی زیر قسمت های مؤلفه نگاه خواهیم کرد.
درک سناریوهای تشخیص تغییر
برای استفاده مؤثر از OnPush ، مهم است که درک کنیم که چگونه تشخیص تغییر Angular در سناریوهای مختلف رفتار می کند. در زیر ما سناریوهای کلیدی و چگونگی تأثیر استراتژی onPush را در هر مورد بررسی یا رد می کند.
1. وقایع در یک مؤلفه با تشخیص تغییر پیش فرض
هنگامی که یک رویداد در یک مؤلفه انجام می شود که از آن استفاده می کند پیش فرض تغییر تشخیص (رفتار معمول) ، Angular تشخیص تغییر را برای کل درخت مؤلفه از ریشه به پایین (Skipping Component Subtrees • Angular). این چیزی در مورد آنچه ممکن است تغییر کرده باشد فرض نمی کند ، بنابراین همه چیز را بررسی می کند. با این حال ، حتی در این حالت ، Angular در مورد فرزندان OnPush هوشمند است: هر زیر درختی که در یک جزء OnPush ریشه دارد ، خواهد بود پر از اگر این مؤلفه onPush در طی این چرخه ورودی های جدیدی دریافت نکرده باشد (Skipping Component Subtrees • Angular).
این به چه معنی است؟ فرض کنید شما یک جزء والدین پیش فرض دارید که حاوی برخی از مؤلفه های کودک OnPush است. اگر کاربر روی یک دکمه در والدین کلیک کند (ایجاد یک رویداد در مؤلفه پیش فرض) ، Angular از طریق تمام مؤلفه ها اجرا می شود. والدین و سایر اجزای پیش فرض طبق معمول به روز می شوند. برای هر کودک OnPush ، Angular بررسی می کند که آیا ورودی های آن تغییر کرده است:
- اگر هیچ ورودی جدیدی وجود ندارد به آن کودک OnPush منتقل شدند ، Angular برای صرفه جویی در وقت ، از بررسی آن کودک و زیر درخت آن استفاده می کند (پرش از زیر قطعات • زاویه ای).
- اگر رویداد والدین باعث شود مقدار ورودی جدیدی به فرزند OnPush وارد شود ، آن کودک بررسی می شود (از آنجا که شرایط “ورودی جدید” را برآورده می کند).
به طور خلاصه ، یک رویداد در یک مؤلفه پیش فرض باعث می شود یک تغییر کامل تغییر کند ، اما اجزای OnPush فقط در صورت دریافت ورودی جدید ، خط را به روز می کنند. در غیر این صورت ، این زیرزمین ها بدون تغییر باقی می مانند (در اصل برای آن چرخه “یخ زده” هستند که برای عملکرد مفید است).
مثال کد: پدر و مادر پیش فرض با یک فرزند onPushبشر در مثال زیر ، مؤلفه والدین از تشخیص پیش فرض تغییر استفاده می کند و دارای فرزند OnPush است. دکمه والدین کلیک می کند باعث تغییر در برنامه می شود. کودک Onpush ngDoCheck
اراده نه روی کلیک کنید زیرا ورودی آن (data
) در حال تغییر نیست.
// OnPush child component
@Component({
selector: "child-comp",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `Child Value: {{ data.value }}
`,
})
export class ChildComponent {
@Input() data: { value: number };
ngDoCheck() {
console.log("ChildComponent checked");
}
}
// Default change detection parent
@Component({
selector: "parent-comp",
template: `
`,
})
export class ParentComponent {
staticData = { value: 42 };
onClick() {
console.log("ParentComponent clicked");
// No change to staticData input; just an event trigger
}
}
در اینجا ، با کلیک بر روی دکمه های “ParentComponent کلیک کنید”. اجراهای زاویه ای تشخیص را تغییر می دهد. ParentComponent
بررسی می شود (زیرا پیش فرض است) ، اما ChildComponent
onpush و input آن است staticData
هنوز هم به همان شیء اشاره می کند. بررسی های زاویه ای ChildComponent
نمای از آن زمان هیچ مرجع ورودی جدید وجود ندارد منتقل شد (پرش از زیر قطعات • زاویه ای). کنسول خواهد شد نه نمایش “ChildComponent بررسی شده” در این کلیک ها ، نشان می دهد که کودک OnPush از بین رفته است. (اگر ParentComponent
تغییر کرده بود staticData
سپس مرجع ChildComponent
بررسی و به روز می شود.)
2. وقایع در یک مؤلفه با OnPush
اکنون یک رویداد (مانند یک کلیک) را در داخل یک مؤلفه که خود از آن استفاده می کند ، در نظر بگیرید اپشکربشر در این سناریو ، Angular هنوز هم چرخه تشخیص تغییر را برای کل کاربرد ایجاد می کند (Zone.js همیشه در هر رویداد تشخیص تغییر برای کل درخت را آغاز می کند). تفاوت این است که در واقع مؤلفه ها به روز می شوند:
- مؤلفه ONPUSH که این رویداد را داشت (و فرزندانش) همیشه بررسی می شود زیرا این رویداد آن را به عنوان “کثیف” نشان می دهد (یک رویداد به عنوان یک عامل تغییر در زیر درخت آن مؤلفه شمرده می شود) (Skipping Component Subtrees • Angular).
- قسمت های دیگر درخت نه در زیر درخت آن مؤلفه رد خواهد شد اگر آنها در حال ورود به سیستم هستند و هیچ ورودی جدیدی ندارند (Skipping Component Subtrees • Angular).
به عبارت دیگر ، زاویه ای از طریق درخت مؤلفه عبور می کند ، اما زیر درختان Onpush دیگر را که تحت تأثیر این رویداد قرار نمی گیرند ، نادیده می گیردبشر هر مؤلفه پیش فرض در هر کجا هنوز اجرا خواهد شد (از آنجا که پیش فرض همیشه اجرا می شود) ، اما اجزای OnPush غیر مرتبط با این رویداد بدون تغییر باقی می مانند.
یک مثال مشترک: فرض کنید مؤلفه ریشه شما حاوی دو مؤلفه جداگانه OnPush (خواهر و برادر) است. اگر یک رویداد در یکی از آنها اتفاق بیفتد ، دیگر مؤلفه OnPush (که هیچ ورودی دریافت نکرد و بخشی از این رویداد نبود) در هنگام تشخیص تغییر (Skipping Component Component • Angular) رد می شود. به این ترتیب ، زاویه ای کار را به شاخه ای از درخت که در آن رویداد رخ داده محدود می کند.
مثال کد: دو خواهر و برادر Onpush ، یک رویداد در یکبشر در مثال زیر ، MainComponent
وت SideComponent
هم توسط والدین و هم توسط والدین ارائه می شوند. یک دکمه روی داخل کلیک کنید MainComponent
یک رویداد را تحریک می کند.
@Component({
selector: "main-comp",
changeDetection: ChangeDetectionStrategy.OnPush,
template: ``,
})
export class MainComponent {
onClick() {
console.log("MainComponent button clicked");
}
ngDoCheck() {
console.log("MainComponent checked");
}
}
@Component({
selector: "side-comp",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `Side static content
`,
})
export class SideComponent {
ngDoCheck() {
console.log("SideComponent checked");
}
}
// Parent template using both
@Component({
template: ` `,
})
export class AppComponent {}
اگر کاربر روی دکمه کلیک کند MainComponent
، خواهیم دید “دکمه اصلی کلیک شده” و سپس “MainComponent بررسی شده” در کنسول. ما نخواهد کرد به “SideComponent بررسی شده” مراجعه کنید زیرا SideComponent
(Onpush) هیچ دلیلی برای دویدن نداشت. اجراهای زاویه ای تشخیص را برای کل درخت تغییر می دهد ، اما آن نادیده گرفتن در SideComponent
از آنجا که این قسمت بدون ورودی جدید است و این رویداد در خارج از آن رخ داده است (Skipping Component Subtrees • Angular). بنابراین ، SideComponent
کاملاً در آن چرخه رد می شود. (اگر SideComponent
به طور پیش فرض بود ، بدون در نظر گرفتن هر چرخه اجرا می شد.)
3. حوادث در نوادگان یک مؤلفه onPush
این سناریو شامل اجزای Onpush تو در تو است: والدین OnPush با کودک OnPush (یا پیش فرض) ، جایی که یک رویداد در کودک سرچشمه می گیرد. به عنوان مثال ، تصور کنید ParentComponent
onpush است و در داخل آن یک است ChildComponent
(که می تواند OnPush باشد). اگر یک رویداد (مانند یک کلیک) در کودک اتفاق بیفتد ، چگونه بر والدین تأثیر می گذارد؟
در زاویه ای ، رویدادها از طریق درخت مؤلفه حباب می شوند، و Angular کل زنجیره را تا ریشه به عنوان نیاز به تشخیص تغییر نشان می دهد. بنابراین ، اگر یک رویداد در یک نوادگان از یک مؤلفه onPush انجام شود ، آن مؤلفه اجداد Onpush بررسی خواهد شد همچنین ، حتی اگر ورودی های آن تغییر نکرد (پرش از زیر قطعات • زاویه ای). در مثال ما ، رویداد در کودک باعث می شود که زاویه ای کودک را بررسی کند (بدیهی است) وت والدین Onpush ، زیرا کودک بخشی از دیدگاه والدین است.
بنابراین ، یک رویداد در یک زیر زمینی OnPush تضمین می کند که Subtree از بین نرود – مرز OnPush به طور موثری توسط این رویداد نقض می شود. Angular تشخیص تغییر را برای کودک ، والدین OnPush و به سمت بالا از طریق هر اجداد دیگر (پیش فرض یا OnPush) اجرا خواهد کرد. این منطقی است: اگر اتفاقی در کودک رخ دهد ، ممکن است والدین نیز نیاز به بروزرسانی داشته باشند (به عنوان مثال ، شاید الگوی والدین نیز به برخی از دارایی هایی که می تواند در نتیجه رویداد کودک تغییر کند ، متصل شود).
مثال کد: والدین و کودک Onpush ، رویداد در کودکبشر در زیر ، والدین و کودک از OnPush استفاده می کنند:
@Component({
selector: "child-comp",
changeDetection: ChangeDetectionStrategy.OnPush,
template: ``,
})
export class ChildComponent {
onChildClick() {
console.log("ChildComponent button clicked");
}
ngDoCheck() {
console.log("ChildComponent checked");
}
}
@Component({
selector: "parent-comp",
changeDetection: ChangeDetectionStrategy.OnPush,
template: ` `,
})
export class ParentComponent {
ngDoCheck() {
console.log("ParentComponent checked");
}
}
اگر کاربر روی دکمه کلیک کند ChildComponent
، زاویه ای آن رویداد را پردازش می کند. حتی ParentComponent
onpush و بدون تغییر مستقیم ورودی ، عمل رسیدگی به یک رویداد در فرزندان آن ، آن را برای بررسی نشان می دهد. در کنسول ، هر دو “ChildComponent بررسی شده” را مشاهده خواهید کرد وت “PARENTCOMPONENT CHECKED”. والدین از بین نرفتند – زاویه ای آن را بررسی کرد زیرا این رویداد از نظر سلسله مراتب آن اتفاق افتاد (از زیر مجموعه های جزء • زاویه ای). این تأیید می کند که یک مؤلفه onPush همیشه در هنگام وقوع یک رویداد در هر نقطه از زیر درخت خود بررسی می شود.
4. دریافت ورودی های جدید در یک مؤلفه onPush
سناریوی نهایی نهایی زمانی است که یک مؤلفه onPush مقدار جدیدی را برای یکی از آن دریافت می کند @Input
خواص والدین آن. این یکی از محرک های اصلی تشخیص تغییر OnPush است. Angular مقدار ورودی جدید را با مقدار قبلی مقایسه می کند. اگر متفاوت باشد (با مرجع یا مقدار اولیه) ، Angular این مؤلفه onPush را که نیاز به بررسی دارد (استفاده از زیر قطعات • زاویه ای) نشان می دهد.
آنچه اتفاق می افتد این است که در طول چرخه تشخیص تغییر بعدی ، Angular تشخیص تغییر را برای آن مؤلفه (و فرزندانش) انجام می دهد. مهمتر ، این کار را انجام می دهد نه بررسی نیروهای دیگر اجزای خواهر و برادر. این فقط بر زیر درخت ریشه در مؤلفه ای که ورودی جدید را بدست آورده است ، تأثیر می گذارد.
به عنوان مثال ، اگر ParentComponent
(می تواند به طور پیش فرض یا onPush باشد) یک شیء جدید یا مقدار بدوی جدید را به آن منتقل می کند ChildComponent
که Onpush است ، Angular بررسی خواهد کرد ChildComponent
(اتصالات آن را در نمای به روز کنید) در چرخه بعدی. سایر مؤلفه های OnPush که ورودی های جدیدی را دریافت نکردند ، دست نخورده باقی می مانند (از زیر هواپیماهای اجزای پرش • زاویه ای).
مثال کد: والدین ورودی جدیدی را برای OnPush Child ارائه می دهندبشر در این مثال ، کودک روی آن قرار دارد و به سادگی مقدار ورودی را نشان می دهد. والدین (می توانند در اینجا پیش فرض باشند) ورودی را به صورت دوره ای به روز می کنند:
@Component({
selector: "child-comp",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `Counter: {{ counter }}
`,
})
export class ChildComponent {
@Input() counter!: number;
ngOnChanges() {
console.log("ChildComponent input changed to", this.counter);
}
}
@Component({
selector: "parent-comp",
template: `
`,
})
export class ParentComponent {
count = 0;
increment() {
this.count++;
}
}
هر بار که دکمه والدین کلیک می شود ، ParentComponent.increment()
اجرا می شود و ارزش آن را تغییر می دهد count
بشر این مقدار جدید به داخل جریان می یابد ChildComponent
از طریق اتصال [counter]="count"
بشر پس از ChildComponent
onpush است و آن یک مقدار ورودی جدید دریافت کرد، زاویه ای تشخیص تغییر را برای ChildComponent
در آن چرخه (پرش از زیر درختان اجزای • زاویه ای). خواهید دید ngOnChanges
هر بار مقدار پیشخوان جدید را وارد کنید. فقط ChildComponent
Subtree در پاسخ به این تغییر ورودی بررسی می شود. اگر سایر اجزای OnPush در جاهای دیگر وجود داشته باشد که ورودی ها یا رویدادهای جدیدی را دریافت نکند ، آنها اجرا نمی شوند. اگر ChildComponent
بچه های OnPush خود را داشتند ، آنها نیز فقط در صورت تغییر ورودی های آنها یا وقایع خاص خود را به روز می کردند.
یک نکته قابل توجه: چک با تغییر مرجع یا ارزش بدویبشر اگر count
یک شیء بود و ما بدون اختصاص یک شیء جدید ، یکی از خصوصیات آن را جهش دادیم ، مقایسه ورودی Angular ممکن است آن را جلب نکند. بعداً این مشکل را پوشش خواهیم داد.
با توجه به این سناریوها ، می بینید که استفاده از قسمتهای استراتژیک “از نظر استراتژیک” از درختان مؤلفه از چک های بی نیاز استفاده می کند. در مرحله بعد ، ما به این موضوع می پردازیم که چگونه می توان از این کار برای عملکرد استفاده کرد و چه شیوه هایی را باید هنگام استفاده از OnPush در برنامه های زاویه ای خود دنبال کنیم.
بهینه سازی عملکرد و بهترین شیوه ها
با استفاده از ChangeDetectionStrategy.OnPush
می تواند با کاهش میزان کار زاویه ای در هر چرخه تشخیص تغییر ، عملکرد را به میزان قابل توجهی بهبود بخشد. در اینجا بهترین روشها و نکات برای استفاده موثر از OnPush آورده شده است:
-
اتخاذ ناپذیری برای ورودی ها: هنگام استفاده از ONPUSH ، توصیه می شود داده های ورودی را تغییر ناپذیر کنید. این بدان معناست که به جای تغییر دادن اشیاء/آرایه ها ، هنگام تغییر داده ها ، موارد جدیدی ایجاد کنید. به این ترتیب ، هنگامی که داده های جدید را به یک مؤلفه onPush منتقل می کنید ، مرجع ورودی تغییر می کند و Angular می داند که این مؤلفه را به روز کند. به عنوان مثال ، اگر شما
@Input() items: Item[]
و می خواهید آن را به روز کنید ، ترجیح می دهیدthis.items = [...this.items, newItem]
بیش ازthis.items.push(newItem)
بشر اولی مرجع را تغییر می دهد و باعث تشخیص OnPush می شود ، در حالی که دومی در جای خود (همان مرجع) جهش می یابد و باعث بروزرسانی نمی شود. -
از OnPush برای اجزای خالص/ارائه استفاده کنید: مؤلفه هایی که به عنوان مؤلفه های ارائه دهنده (گنگ) عمل می کنند یا در درجه اول داده ها را بر اساس inputs نمایش می دهند ، کاندیداهای خوبی برای OnPush هستند. آنها وضعیت داخلی خود را زیاد مدیریت نمی کنند. آنها فقط ورودی ها را ارائه می دهند و شاید خروجی ها را منتشر کنند. علامت گذاری به آنها در Push تضمین می کند که آنها فقط وقتی داده های ورودی تغییر می کنند یا در پاسخ به تعامل کاربر در آنها تغییر می کنند ، مجدداً ارائه می دهند. این می تواند چک های غیر ضروری را برای لیست های بزرگ قطعات (مانند لیستی از ردیف های مورد و غیره) کاهش دهد.
-
لوله Async را اهرم کنید: هنگام برخورد با مشاهدات در زاویه ای ، با استفاده از
async
لوله در الگوی بهترین روش است – به خصوص با OnPush. لوله async در یک مقادیر قابل مشاهده و فشار به الگوی مشترک خواهد بود و از نظر مهم ، تماس خواهد گرفتmarkForCheck()
در مورد مؤلفه هنگامی که یک مقدار جدید منتشر می شود (زاویه ای: واکنش پذیری آزمون با استراتژی onPush | حاشیه لاکلاکو)بشر این بدان معناست که مؤلفه OnPush شما هنگام انتشار قابل مشاهده ، بدون هیچ گونه مداخله دستی ، به درستی به روز می شود. این تمیزتر از عضویت در کلاس مؤلفه و سپس تماس با آن استchangeDetectorRef.markForCheck()
خودت به طور خلاصه ، لوله async + onpush ترکیبی قدرتمند برای به روزرسانی های اتوماتیک و کارآمد UI است. -
بدانید چه موقع به صورت دستی باعث تغییر تغییر می شود: حتی با وجود OnPush ، مواقعی وجود دارد که لازم است به صورت دستی به زاویه ای بگویید تا یک مؤلفه را بررسی کنید. دو روش اصلی عبارتند از:
-
ChangeDetectorRef.markForCheck()
: این مؤلفه (و اجداد آن) را به عنوان کثیف نشان می دهد ، به طوری طرف دیگر چرخه تشخیص تغییر ، Angular آنها را در بررسی قرار می دهد (TypeScript – Angular Markforcheck vs DetectChanges – سرریز پشته). از این استفاده کنید وقتی برخی از حالت ها را به روز کرده اید که Angular به آن دست نیافته است (به عنوان مثال شما به طور ضمنی ورودی یک مؤلفه یا ساختار داده محدود را تغییر داده اید) و می خواهید Angular آن را در دور بعدی انتخاب کنید. علامت گذاری برای چک است غیر همزمان با توجه به اجرای کد فعلی – بلافاصله تشخیص تغییر را اجرا نمی کند ، فقط مؤلفه را برای بررسی به زودی برنامه ریزی می کند. -
ChangeDetectorRef.detectChanges()
: این روش بلافاصله محرک ها تشخیص این مؤلفه را تغییر می دهند و فرزندان آن ، همزمان ، در لحظه ای که آن را صدا می کنید (TypeScript – Angular Markforcheck vs DetectChanges – Overflow Stack). این مانند این است که به زاویه ای بگویید “همین حالا این دیدگاه را بررسی کنید.” این امر می تواند مفید باشد اگر شما باید اطمینان حاصل کنید که UI برخی از تغییرات را فوراً منعکس می کند (به عنوان مثال ، پس از یک تماس تلفنی در خارج از منطقه Angular یا در برخی از سناریوهای زمان بندی پیچیده). با این حال ، شما باید از این کمبود استفاده کنید – تماس بگیریدdetectChanges()
غالباً یا در یک حلقه می تواند به عملکرد آسیب برساند ، زیرا از دسته معمولی Angular عبور می کند. همچنین ، اگر در یک چرخه تشخیص تغییر مداوم استفاده شود ، می تواند منجر به “بیان بدنام پس از بررسی” شود “اگر دقیق نباشد ، خطای را تغییر داده است (زیرا ممکن است چک های تو در تو را ایجاد کنید).
-
-
از کدام یک استفاده کنید؟ به طور کلی ، ترجیح می دهید
markForCheck()
در بیشتر موارد هنگام برخورد با اجزای OnPush. این برنامه با برنامه تشخیص تغییر طبیعی Angular کار می کند و حداقل چک ها را تضمین می کند (فقط شاخه مورد نیاز را در چرخه بعدی دوباره بررسی می کند و در صورت وقوع تغییرات چندگانه را با هم جمع می کند). استفاده کردنdetectChanges()
اگر نیاز خاصی به مجبور کردن یک چک فوری یا جداسازی تغییر تغییر به قسمت کوچکی از درخت مؤلفه دارید. به عنوان مثال ، پس از پاسخ به تماس خارجی (مانند یک Settimeout در خارج از NGZone Angular) ، ممکن است تماس بگیریدdetectChanges()
برای به روزرسانی مشاهده بلافاصله. اگر خود را در تماس می گیریدdetectChanges()
خیلی اوقات ، در نظر بگیرید که آیا می توانید کد خود را تغییر دهید تا به جای آن به مکانیسم عادی Angular یا لوله Async اعتماد کنید – بیش از حد تماس های دستی می تواند بوی کد باشد که نشان می دهد شما در حال مبارزه با چارچوب هستید. -
در بیشتر موارد از تغییر etectorref خودداری کنید: در حالت ایده آل ، اگر ورودی های مؤلفه و جریان داده های خود را به روشی دوستانه زاویه ای (با استفاده از داده های تغییر ناپذیر و لوله های Async) طراحی کنید ، به ندرت نیاز به تزریق خواهید داشت
ChangeDetectorRef
و این روش ها را به صورت دستی بنامید. چارچوب آن را اداره می کند. استفاده از آنها “اشتباه” نیست – آنها برای موارد معتبر وجود دارند – اما اگر به شدت به آن اعتماد کنیدmarkForCheck()
یاdetectChanges()
اگر یک رویکرد اصطلاحات بیشتر وجود داشته باشد ، دوبار بررسی کنید. به عنوان مثال ، اگر فهمیدید که باید تماس بگیریدmarkForCheck()
پس از جهش خاصیت input ، این یک اشاره است که شما باید به جای جهش ، input را تغییر دهید. -
اندازه گیری سود عملکرد: OnPush می تواند میزان کار در هر کنه را کاهش دهد ، اما همچنین پیچیدگی هایی را به کد شما اضافه می کند (شما باید مدیریت تغییر آگاهانه را مدیریت کنید). از ابزارهایی مانند Angular DevTools یا جدول زمانی عملکرد استفاده کنید تا در صورت استفاده از ONPUSH در قسمت های خاصی از برنامه شما ، عملکرد عملکرد قابل مشاهده را داشته باشد. تمرکز خود را در جایی که دستاوردها قابل توجه است (لیست های بزرگ ، به روزرسانی های مکرر و غیره). نیازی به استفاده از OnPush به طور پیش فرض نیست – بسیاری از برنامه ها با استراتژی پیش فرض بسیار خوب عمل می کنند ، اما OnPush در صورت نیاز به تقویت اضافی وجود دارد.
با پیروی از این شیوه ها – استفاده از الگوهای داده غیرقابل تغییر ، لوله Async و تشخیص تغییر دستی انتخابات – می توانید از روی استفاده از آن استفاده کنید تا کاربرد زاویه ای شما بیشتر اجرا شود. در مرحله بعد ، ما در مورد برخی از موارد لبه پیچیده و مشکلات مورد بحث قرار خواهیم گرفت تا هنگام استفاده از OnPush از آن آگاه باشیم.
موارد لبه و مشکلات رایج
در حالی که OnPush می تواند برنامه شما را تسریع کند ، اگر از برخی از GOTCHA ها آگاه نیستید ، ممکن است شما را شگفت زده کند. در اینجا برخی از مشکلات رایج و نحوه رسیدگی به آنها وجود دارد:
-
جهش ورودی شیء بدون تغییر مرجع: اگر یک مؤلفه onPush به عنوان input یک شی (یا آرایه) دریافت کند و شما یک ویژگی از آن شی را جهش دهید بدون اختصاص یک شیء جدید، زاویه ای هیچ تغییری را تشخیص نمی دهد. این امر به این دلیل است که مرجع شیء یکسان است (
===
مقایسه هیچ تفاوتی نمی بیند) (پرش از زیر قطعات • زاویه ای). -
به روزرسانی دستی خصوصیات input: بعضی اوقات ممکن است نمونه ای از مؤلفه کودک را از طریق بدست آورید
@ViewChild
و یک ویژگی input را مستقیماً به صورت کد تنظیم کنید ، یا در غیر این صورت یک ویژگی ورودی را در خارج از الگوی عادی تنظیم کنید. اگر آن کودک در حال کار باشد ، Angular به طور خودکار نمی داند تشخیص تغییر را برای آن انجام دهد ، زیرا از طریق مکانیسم اتصال معمول به روز نشده است. کودک Onpush در حالت قبلی خود باقی مانده است (از زیر درختان جزء • زاویه ای). به عنوان مثال:@ViewChild(ChildComp) child: ChildComp; ... this.child.someInput = newVal; this.childCdr.markForCheck();
بشر از طرف دیگر ، طراحی مجدد داده ها از طریق اتصال ورودی یا سرویس را بنابراین زاویه ای از تغییر آگاه است. -
استفاده از مشاهدات با OnPush (بدون لوله Async): اگر در یک مؤلفه onPush (در کلاس کامپوننت) قابل مشاهده هستید و در هنگام انتشار برخی از حالت ها را به روز می کنید ، این امر به طور خودکار باعث تشخیص تغییر نمی شود. به عنوان مثال ، شما یک سرویس تزریق می کنید ، در یک جریان مشترک مشترک می شوید
ngOnInit
، و یک قسمت کامپوننت را تنظیم کنید. در Onpush ، مگر اینکه آن تماس با تماس تلفنی تماس بگیریدmarkForCheck()
یا مقدار از طریق ورودی منتقل می شود ، مشاهده ممکن است به روز نشود. راه حل: باز هم ، لوله Async دوست شما است – اشتراک و علامت گذاری برای بررسی برای شما. اما اگر باید در کد مشترک شوید (شاید برای ترکیب جریان ها یا استفاده از یک اشتراک واحد برای مقادیر چندگانه) ، سپس اطمینان حاصل کنید که تماس بگیریدthis.cdr.markForCheck()
در داخل اشتراک مشترک برای اطلاع از Angular از داده های جدید. به این ترتیب ، مؤلفه ONPush وقتی که قابل مشاهده یک مقدار را شلیک می کند ، الگوی آن را در چرخه بعدی بررسی می کند. (از طرف دیگر ، استفاده را در نظر بگیریدdetectChanges()
اگر بلافاصله در طی آن پاسخ به اشتراک به روزرسانی نیاز دارید ، اگرچه معمولاً Markforcheck کافی است.) -
لوله های چندگانه Async: یک یادداشت مرتبط – لوله Async برای بررسی هر انتشار جدید OnPush را علامت گذاری می کند. اگر یک قابل مشاهده به طور مکرر منتشر شود (به عنوان مثال ، چند بار در ثانیه) ، هر انتشار یک تغییر تغییر را برنامه ریزی می کند. این معمولاً خوب است (زاویه ای می تواند به سرعت چک های زیادی را انجام دهد) ، اما به جریان های با فرکانس بسیار بالا توجه داشته باشید ، زیرا اگر کار UI هر بار سنگین باشد ، می توانند باعث ایجاد مشکلات عملکرد شوند. در چنین مواردی ، در نظر بگیرید که جریان را از بین ببرید/از جریان استفاده کنید یا از استراتژی هایی برای رها کردن فریم ها استفاده کنید (در صورت وجود).
-
فراخوانی
detectChanges()
در زمان اشتباه: اگر تماس بگیریدchangeDetectorRef.detectChanges()
در حالی که زاویه ای در حال حاضر در وسط چرخه تشخیص تغییر قرار دارد (به عنوان مثال ، از درونngOnInit
از کودکی در حالی که والدین هنوز در حال بررسی هستند) ، می توانید وارد شوید ExpressChangedafterithasBeenChecked خطا این امر به این دلیل است که شما در وسط بررسی طبیعی Angular یک چک اضافی را مجبور می کنید و قبل از مقایسه آن را گیج می کنید. برای جلوگیری از این ، فقط تماس بگیریدdetectChanges()
در مکان ها Angular به طور فعال در حال بررسی نیست (مانند درsetTimeout
پاسخ به تماس ، یا در پاسخ به رویدادی که Angular در مورد آن نمی داند). استفاده از رویکرد ایمن تر در صورت نیاز به ایجاد یک بررسی اضافی در هنگام اولیه سازی ، استفاده از آن استsetTimeout(() => cdr.detectChanges())
یاPromise.resolve().then(() => cdr.detectChanges())
پس از انجام چرخه زاویه ای ، آن را به ماکروتاسک بعدی تعویق کنید. یا به سادگی مؤلفه ای را طراحی کنید که به این امر احتیاج نداشته باشد. -
تشخیص پیش فرض تغییر در داخل ONPUSH Subtree: اگر ترکیبی از استراتژی ها را دارید (برخی از مؤلفه های کودک به طور پیش فرض ، برخی از OnPush) ، به یاد داشته باشید که یک کودک پیش فرض استراتژی در داخل یک والدین OnPush هنوز هم خواهد بود دویدن اگر پدر و مادر OnPush پرش شدند. اگر والدین OnPush دلیلی برای اجرای آن نداشته باشند ، کل زیر درخت از بین می رود. این می تواند یک مشکل باشد: شما ممکن است انتظار داشته باشید که یک کودک پیش فرض همیشه بررسی کند ، اما اگر اجداد OnPush آن در حال اجرا نباشد ، نیز بررسی نمی شود. در اصل ، Onpush “Trickles Down” – اگر پدر و مادر پرش شوند ، همه فرزندان آن بدون در نظر گرفتن استراتژی خودشان پرش می شوند. راه حل: اطمینان حاصل کنید که اگر برای به روزرسانی به یک کودک پیش فرض متکی هستید ، والدین OnPush به طور مناسب کثیف مشخص می شوند (شاید از طریق
markForCheck()
هنگامی که کودک نیاز به به روزرسانی دارد). از طرف دیگر ، در نظر بگیرید که کودک را نیز برای قوام در نظر بگیرید و به روزرسانی های خود را از طریق ورودی یا رویدادها مدیریت کنید.
آگاهی از این موارد لبه به شما کمک می کند تا از اشکالات ناامید کننده در جایی که UI به روز نمی شود ، خودداری کنید. بیشتر اینها به یک قانون ساده جوش می خورند: با استفاده از ONPUSH ، همیشه وقتی می خواهید به روزرسانی کنید ، منابع شی را تغییر دهید ، یا صریحاً به Angular بگویید که چیزی تغییر می کند. اگر این قانون را رعایت کنید ، به ندرت به موضوعاتی می پردازید.
نمودارها و کمکهای بصری
بیایید تجسم کنیم که چگونه OnPush می تواند از زیر درختان در یک درخت مؤثر پرش کند. ساختار درخت مؤلفه زیر را در نظر بگیرید (اجزای OnPush مشخص شده با “(OnPush)”):
AppComponent
├── HeaderComponent
├── MainComponent (OnPush)
│ ├── SearchComponent
│ └── ButtonComponent
└── LoginComponent (OnPush)
└── DetailsComponent
در یک چرخه تشخیص تغییر عادی (استراتژی پیش فرض در همه جا) ، اگر یک رویداد در هر جایی اتفاق بیفتد ، Angular هر مؤلفه را از آن بررسی می کند AppComponent
به سمت DetailsComponent
بشر حال ، بیایید نشان دهیم که چگونه Onpush این را تغییر می دهد:
-
رویداد در یک مؤلفه پیش فرض (به عنوان مثال headerComponent): زاویه ای در شروع می شود
AppComponent
و پایین می رود. بررسی می کندHeaderComponent
(جایی که این رویداد رخ داده است) و ادامه دهید. وقتی بهMainComponent (OnPush)
، از آنجا کهMainComponent
هیچ نوع جدید input ، زاویه ای دریافت نکرد پرش کلMainComponent
Subtree (Skipping Component Subtrees • Angular). نتیجه با اجتناب از چک های بی نیاز عملکرد بهتر استMainComponent
و فرزندانش -
رویداد در یک مؤلفه onPush (به عنوان مثال MainComponent): Angular چرخه را برای کل درخت اجرا می کند ، اما اکنون این رویداد اتفاق افتاده است
MainComponent
بشر اینقدرMainComponent
بررسی خواهد شد (به دلیل این رویداد کثیف است). فرزندانشSearchComponent
وتButtonComponent
همچنین بررسی می شود (از آنجا که زیر درخت فعال است). در ضمن ،LoginComponent
(شاخه دیگر OnPush) بخشی از زیرزمین این رویداد نیست و هیچ ورودی جدیدی ندارد ، بنابراین زاویه ای پرشLoginComponent
و فرزند آن (از زیر درختان اجزای پرش • زاویه ای). در واقع ،MainComponent
شاخه اجرا می شود ،LoginComponent
این بار از شاخه نادیده گرفته می شود. -
رویداد در نوادگان یک OnPush (به عنوان مثال یک دکمه در داخل logincomponent): فرض کنید این رویداد عمیق است
LoginComponent
الگوی S (logincomponent onpush است). زاویه ای علامت گذاری خواهد کردLoginComponent
و همچنین اجداد آن برای بررسی. اینقدرLoginComponent
دوید ، و به دلیل اینکه بخشی از آن استMainComponent
نمای ، زاویه ای نیز بررسی خواهد کردMainComponent
(حتی اگرMainComponent
خود به طور مستقیم این رویداد را دریافت نکرد)) (Skipping Component Subtrees • Angular). در این چرخه ، همه چیز موجود در درخت به پایان می رسد به جز هر زیر درخت مستقل OnPush که درگیر آن نیستند (در این حالت ،MainComponent
بود درگیر به عنوان والدین ؛ اگر یک شاخه جداگانه Onpush وجود داشته باشد ، این رد می شود). -
ورودی جدید به ONPush (به عنوان مثال AppComponent داده های جدید را به MainComponent منتقل می کند): کی
AppComponent
به روزرسانی input محدود بهMainComponent
، اراده زاویه ای ، در چرخه بعدی ، بررسی کنیدMainComponent
و فرزندان آن (Skipping Component Subtrees • Angular). اگرLoginComponent
چیز جدیدی پیدا نکرد ، آن را رد می کند. اساساً ، فقطMainComponent
طراوت های فرعی. اگرLoginComponent
همچنین یک ورودی جدید (می گویند AppComponent نیز چیز جدیدی را به آن منتقل کرده است) ، سپس آن را نیز تازه می کند. OnPush تضمین می کند که هر زیر درخت فقط در صورتی که ورودی های مستقیم آن به آن بگویند ، تازه می کند.
این سناریوها را می توان به عنوان بخش هایی از درخت “روشن” یا “خاموش” برای تشخیص تغییر بر اساس محرک ها تجسم کرد. با نگاهی به این درخت ، می توانید درک کنید که چگونه وقایع یا تغییرات ورودی باعث می شود شاخه های خاصی به روز شوند در حالی که دیگران دست نخورده باقی می مانند. این مدل بصری مدل ذهنی را تقویت می کند: onPush = زحمت بررسی در اینجا را ندارید مگر اینکه چیزی خاص تغییر کند.
(در یک نمودار زنده یا نمودار جریان ، ما می توانیم برجسته کنیم که کدام مؤلفه ها بررسی می شوند (به عنوان مثال ، سبز) و کدام یک از سناریو را رد می کنند.
سیگنال ها و onpush (زاویه 17+)
با زاویه 16 شروع می شود ، و در زاویه 17 و بالاتر افزایش می یابد ، زاویه ای معرفی می شود نشان – یک مدیرعامل واکنش پذیر دولت که به شدت با استراتژی تشخیص تغییر OnPush ادغام می شود.
سیگنال ها چیست؟
سیگنال ها متغیرهای واکنشی هستند که هنگام تغییر مقدار آنها زاویه ای را اعلام می کنند. هنگامی که از سیگنال در الگوی یک مؤلفه onPush استفاده می شود ، Angular به طور خودکار وابستگی خود را ردیابی می کند و مؤلفه را برای بررسی نشان می دهد وقتی سیگنال تغییر می کند.
چرا سیگنال ها با Onpush خوب کار می کنند
با استفاده از OnPush ، Skips Angular یک مؤلفه را بررسی می کند ، مگر اینکه ورودی های جدید ، یک رویداد یا تشخیص دستی داشته باشد. نشان یک مسیر چهارم را اضافه کنید: مقادیر واکنشی تعبیه شده در خود مؤلفه.
این اجازه می دهد تا مؤلفه ها واکنش پذیر باشند بدون اتصال input یا کتابچه راهنمای کاربر markForCheck()
بشر
مثال: مؤلفه OnPush با یک سیگنال
import { Component, signal } from "@angular/core";
import { ChangeDetectionStrategy } from "@angular/core";
@Component({
selector: "counter-button",
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
Counter: {{ counter() }}
`,
})
export class CounterButtonComponent {
counter = signal(0);
increment() {
this.counter.update((v) => v + 1);
}
}
🔍 توجه: حتی
CounterButtonComponent
onpush است و هیچ ورودی ندارد ، استفاده از آهنگ های زاویه ای ازcounter()
در الگوی و هنگام به روزرسانی ، چک را ایجاد می کند. این از نیاز به آن جلوگیری می کندChangeDetectorRef.markForCheck()
کاملاً
سیگنال ها به عنوان ورودی (زاویه 17+)
زاویه 17 معرفی شده input()
برای تعریف واکنشی @Input()
خواص به عنوان سیگنال:
import { Component, input } from "@angular/core";
@Component({
selector: "child",
standalone: true,
template: `Received: {{ value() }}
`,
})
export class ChildComponent {
value = input<string>();
}
اکنون ، هنگامی که والدین ورودی را به روز می کنند ، Angular سیگنال را به روز می کند – و تغییر OnPush تشخیص آن را به طور خودکار بدون نیاز به محرک های دستی انتخاب می کند.
چه موقع از سیگنال هایی با OnPush استفاده کنید
- هنگامی که شما حالت واکنشی محلی را در داخل اجزای OnPush مدیریت می کنید.
- هنگام ساختن مؤلفه های ارائه دهنده با intactiveNputs.
- هنگام ترکیب مشاهده و سیگنال ها برای به روزرسانی های UI ریز دانه.
بهترین روشها
- ترجیح دادن
signal()
بیش ازBehaviorSubject
یاmanual markForCheck()
برای ایالت UI محلی. - ترکیب کردن
input()
باeffect()
برای ساختن اجزای پاسخگو با جریان داده های تمیز. - استفاده کردن
computed()
برای به دست آوردن حالت بر اساس چندین سیگنال.
با ترکیب سیگنال ها با OnPush ، شما را دریافت می کنید بهترین عملکرد و هم در واکنشبشر
پایان
بهینه سازی تشخیص تغییر زاویه ای با استراتژی ONPush یک روش قدرتمند برای بهبود عملکرد برنامه است. با گفتن Angular برای رد کردن زیر قطعات از اجزای بررسی ، مگر اینکه لازم باشد ، شما کار انجام شده در هر چرخه تشخیص تغییر را کاهش می دهید.
غذای اصلی:
- پیش فرض در مقابل onPush: تشخیص پیش فرض تغییر همه چیز را هر بار (ساده اما بالقوه سنگین) بررسی می کند ، در حالی که OnPush به شما امکان می دهد شاخه های درخت مؤلفه را قطع کنید که در صورت تغییر هیچ چیز در آنها ، از بررسی جدا شود.
- onPush محرک ها: شرایطی را که بررسی مجدد برای یک مؤلفه onPush است – به یاد داشته باشید – منابع جدید ورودی ، وقایع موجود در الگوی یا کودکان ، علائم دستی یا انتشار لوله های Async. اگر هیچ یک از اینها اتفاق بیفتد ، مؤلفه می تواند رقص تشخیص تغییر را بنشیند.
-
سیگنال ها (زاویه 16+): سیگنال ها روشی واکنشی و ایمن برای به روزرسانی UI در اجزای ONPush بدون نیاز به
ChangeDetectorRef
بشر آنها یک جایگزین تمیز و ارگونومیک برایBehaviorSubject
برای دولت محلی و مدیریت ورودی. -
بهترین روشها: از OnPush روی مؤلفه هایی که از آن بهره مند می شوند استفاده کنید (اغلب مؤلفه های ارائه دهنده ، لیست های بزرگ و غیره) ، ورودی های خود را تغییر ناپذیر نگه دارید و ترجیح می دهید از لوله های Async و رویدادهای خروجی برای برقراری ارتباط استفاده کنید. در
ChangeDetectorRef.markForCheck()
هنگامی که شما نیاز به زاویه ای دارید که چیزی تغییر کرده و از آن استفاده کنیدdetectChanges()
فقط هنگامی که کاملاً به یک چک فوری نیاز دارید. - مشکلات مشترک: توجه داشته باشید که اشیاء جهش یافته باعث به روزرسانی نشده اند – همیشه منابع را تغییر دهید یا صریحاً برای بررسی در این موارد علامت گذاری کنید. و اطمینان حاصل کنید که هرگونه تنظیم ورودی دستی یا تزریق داده های خارجی با ماشه تشخیص تغییر لازم همراه است.
با معرفی نشان، اکنون Angular یک سبک واکنشی تر و اعلامی تر از اجزای ساختمان را امکان پذیر می کند – به ویژه در هنگام ترکیب با OnPush. آنها در کنار هم ، به توسعه دهندگان کنترل ریز دانه و کارایی بالا و بدون دیگ بخار بیش از حد می دهند.
با پیروی از این تکنیک ها ، برنامه های زاویه ای مقیاس پذیر ، کارآمدتر و قابل نگهداری تر می نویسید.
برنامه نویسی مبارک!