ใช้การตรวจหาการเปลี่ยนแปลงที่เร็วขึ้นเพื่อประสบการณ์ของผู้ใช้ที่ดียิ่งขึ้น
Angular จะเรียกใช้กลไกการตรวจจับการเปลี่ยนแปลงเป็นระยะๆ เพื่อให้การเปลี่ยนแปลงของโมเดลข้อมูลแสดงในมุมมองของแอป การตรวจหาการเปลี่ยนแปลงสามารถทริกเกอร์ด้วยตนเองหรือผ่านเหตุการณ์แบบไม่พร้อมกัน (เช่น การโต้ตอบของผู้ใช้หรือการดําเนินการ XHR ที่เสร็จสมบูรณ์)
การตรวจหาการเปลี่ยนแปลงเป็นเครื่องมือที่มีประสิทธิภาพ แต่หากทำงานบ่อยมาก อาจทริกเกอร์การประมวลผลจำนวนมากและบล็อกเธรดหลักของเบราว์เซอร์
ในบทความนี้ คุณจะได้เรียนรู้วิธีควบคุมและเพิ่มประสิทธิภาพกลไกการตรวจจับการเปลี่ยนแปลงโดยการข้ามบางส่วนของแอปพลิเคชันและเรียกใช้การตรวจหาการเปลี่ยนแปลงเฉพาะเมื่อจำเป็นเท่านั้น
การตรวจจับการเปลี่ยนแปลงของ Angular
มาดูแอปตัวอย่างเพื่อทำความเข้าใจวิธีการทํางานของการตรวจหาการเปลี่ยนแปลงของ Angular
คุณดูโค้ดสำหรับแอปได้ในที่เก็บ GitHub นี้
แอปแสดงรายชื่อพนักงานจาก 2 แผนกในบริษัท ได้แก่ ฝ่ายขายและฝ่ายวิจัยและพัฒนา และมีองค์ประกอบ 2 อย่าง ดังนี้
AppComponent
ซึ่งเป็นคอมโพเนนต์รูทของแอป และEmployeeListComponent
2 อินสแตนซ์ 1 รายการสําหรับฝ่ายขายและอีก 1 รายการสําหรับฝ่ายวิจัยและพัฒนา
คุณดูอินสแตนซ์ 2 อินสแตนซ์ของ 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
สำหรับการเชื่อมโยงข้อมูลแบบ 2 ทางระหว่างอินพุตกับพร็อพเพอร์ตี้ label
ที่ประกาศใน EmployeeListComponent
ด้วย
ด้วย EmployeeListComponent
2 อินสแตนซ์ แอปจะสร้างแผนผังคอมโพเนนต์ต่อไปนี้
AppComponent
คือคอมโพเนนต์รูทของแอปพลิเคชัน คอมโพเนนต์ย่อยคืออินสแตนซ์ 2 รายการของ EmployeeListComponent
โดยแต่ละอินสแตนซ์จะมีรายการต่างๆ (E1, E2 ฯลฯ) ที่แสดงถึงพนักงานแต่ละคนในแผนก
เมื่อผู้ใช้เริ่มป้อนชื่อพนักงานใหม่ในกล่องอินพุตในEmployeeListComponent
Angular จะทริกเกอร์การตรวจหาการเปลี่ยนแปลงสำหรับทั้งต้นไม้คอมโพเนนต์โดยเริ่มจาก AppComponent
ซึ่งหมายความว่าในขณะที่ผู้ใช้พิมพ์อินพุตข้อความ Angular จะคำนวณค่าตัวเลขที่เชื่อมโยงกับพนักงานแต่ละคนซ้ำๆ เพื่อยืนยันว่าค่าดังกล่าวไม่มีการเปลี่ยนแปลงนับตั้งแต่การตรวจสอบครั้งล่าสุด
หากต้องการดูว่าระบบทำงานช้าเพียงใด ให้เปิดโปรเจ็กต์เวอร์ชันที่ไม่ได้เพิ่มประสิทธิภาพใน StackBlitz แล้วลองป้อนชื่อพนักงาน
คุณสามารถยืนยันว่าการชะลอตัวมาจากฟังก์ชัน fibonacci
โดยการตั้งค่าโปรเจ็กต์ตัวอย่าง แล้วเปิดแท็บประสิทธิภาพของ Chrome DevTools
- กด "Control+Shift+J" (หรือ "Command+Option+J" ใน Mac) เพื่อเปิดเครื่องมือสำหรับนักพัฒนาเว็บ
- คลิกแท็บประสิทธิภาพ
จากนั้นคลิกบันทึก (ที่มุมซ้ายบนของแผงประสิทธิภาพ) และเริ่มพิมพ์ในกล่องข้อความใดก็ได้ในแอป ผ่านไป 2-3 วินาที ให้คลิกบันทึก อีกครั้งเพื่อหยุดบันทึก เมื่อเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ประมวลผลข้อมูลการทำโปรไฟล์ทั้งหมดที่เครื่องมือดังกล่าวรวบรวมไว้ คุณจะเห็นข้อมูลดังนี้
หากมีพนักงานหลายคนในรายการ กระบวนการนี้อาจบล็อกเธรด UI ของเบราว์เซอร์และทําให้เฟรมลดลง ซึ่งทําให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ไม่ดี
การข้ามโครงสร้างย่อยของคอมโพเนนต์
เมื่อผู้ใช้พิมพ์ข้อความสำหรับฝ่ายขาย EmployeeListComponent
คุณจะทราบว่าข้อมูลในแผนกวิจัยและพัฒนาไม่มีการเปลี่ยนแปลง จึงไม่มีเหตุผลที่จะเรียกใช้การตรวจหาการเปลี่ยนแปลงในคอมโพเนนต์ดังกล่าว เพื่อให้มั่นใจว่าอินสแตนซ์ R&D จะไม่ทริกเกอร์การตรวจจับการเปลี่ยนแปลง ให้ตั้งค่า 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 จะยังคงคํานวณค่าตัวเลขสําหรับพนักงานทุกคนในแผนกอีกครั้งเมื่อผู้ใช้พิมพ์อินพุตข้อความที่เกี่ยวข้อง
หากต้องการปรับปรุงลักษณะการทำงานนี้ คุณสามารถใช้ไพป์แบบบริสุทธิ์ ทั้งท่อสะอาดและไม่บริสุทธิ์จะยอมรับอินพุตและผลลัพธ์ที่ใช้ในเทมเพลตได้ ความแตกต่างระหว่างทั้ง 2 แบบคือท่อบริสุทธิ์จะคำนวณผลลัพธ์ใหม่ก็ต่อเมื่อได้รับอินพุตที่แตกต่างจากการเรียกใช้ก่อนหน้า
อย่าลืมว่าแอปจะคำนวณค่าเพื่อแสดงโดยอิงตามค่าตัวเลขของพนักงานโดยใช้เมธอด 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
ดูโค้ดที่มีการเพิ่มประสิทธิภาพ Pure Pipe ของแอปพลิเคชันต้นฉบับได้ที่นี่
บทสรุป
เมื่อรันไทม์ทำงานช้าลงในแอป Angular ให้ทำดังนี้
- ทำโปรไฟล์แอปพลิเคชันด้วยเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เพื่อดูว่าการชะลอตัวมาจากที่ใด
- แนะนำกลยุทธ์การตรวจหาการเปลี่ยนแปลง
OnPush
เพื่อตัดต้นไม้ย่อยของคอมโพเนนต์ - ย้ายการคํานวณที่หนักไปยังไปป์ไลน์แบบบริสุทธิ์เพื่อให้เฟรมเวิร์กแคชค่าที่คำนวณแล้วได้