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