Triển khai tính năng phát hiện thay đổi nhanh hơn để mang lại trải nghiệm tốt hơn cho người dùng.
Angular chạy cơ chế phát hiện thay đổi theo định kỳ để các thay đổi đối với mô hình dữ liệu được phản ánh trong khung hiển thị của ứng dụng. Tính năng phát hiện thay đổi có thể được kích hoạt theo cách thủ công hoặc thông qua một sự kiện không đồng bộ (ví dụ: lượt tương tác của người dùng hoặc lượt hoàn thành XHR).
Phát hiện thay đổi là một công cụ mạnh mẽ, nhưng nếu chạy rất thường xuyên, tính năng này có thể kích hoạt nhiều phép tính và chặn luồng chính của trình duyệt.
Trong bài đăng này, bạn sẽ tìm hiểu cách kiểm soát và tối ưu hoá cơ chế phát hiện thay đổi bằng cách bỏ qua các phần của ứng dụng và chỉ chạy tính năng phát hiện thay đổi khi cần.
Tính năng phát hiện thay đổi của Inside Angular
Để hiểu cách hoạt động của tính năng phát hiện thay đổi của Angular, hãy xem một ứng dụng mẫu!
Bạn có thể tìm thấy mã cho ứng dụng trong kho lưu trữ GitHub.
Ứng dụng này liệt kê các nhân viên ở hai bộ phận trong công ty là bộ phận bán hàng và bộ phận R&D, đồng thời có hai thành phần:
AppComponent
, là thành phần gốc của ứng dụng và- Hai phiên bản của
EmployeeListComponent
, một phiên bản cho doanh số và một phiên bản cho hoạt động nghiên cứu và phát triển.
Bạn có thể thấy hai thực thể của EmployeeListComponent
trong mẫu cho 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>
Mỗi nhân viên sẽ có một tên và một giá trị số. Ứng dụng chuyển giá trị dạng số của nhân viên sang một phép tính kinh doanh và trực quan hoá kết quả trên màn hình.
Giờ hãy xem 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
chấp nhận danh sách nhân viên và tên phòng ban làm dữ liệu đầu vào. Khi người dùng cố gắng xoá hoặc thêm nhân viên, thành phần này sẽ kích hoạt một đầu ra tương ứng. Thành phần này cũng xác định phương thức calculate
. Phương thức này sẽ triển khai phép tính kinh doanh.
Dưới đây là mẫu cho 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>
Mã này lặp lại trên tất cả nhân viên trong danh sách và hiển thị một mục danh sách cho mỗi nhân viên. Lớp này cũng bao gồm cả lệnh ngModel
để liên kết dữ liệu hai chiều giữa dữ liệu đầu vào và thuộc tính label
được khai báo trong EmployeeListComponent
.
Với hai thực thể của EmployeeListComponent
, ứng dụng sẽ tạo thành cây thành phần sau:
AppComponent
là thành phần gốc của ứng dụng. Thành phần con là 2 thực thể của EmployeeListComponent
. Mỗi phiên bản có một danh sách các mục (E1, E2, v.v.) đại diện cho từng nhân viên trong phòng ban.
Khi người dùng bắt đầu nhập tên của một nhân viên mới vào hộp nhập trong EmployeeListComponent
, Angular sẽ kích hoạt tính năng phát hiện thay đổi cho toàn bộ cây thành phần bắt đầu từ AppComponent
. Điều này có nghĩa là trong khi người dùng nhập văn bản, Angular sẽ liên tục tính toán lại các giá trị số liên kết với từng nhân viên để xác minh rằng người dùng không thay đổi kể từ lần kiểm tra gần đây nhất.
Để xem quá trình này chậm đến mức nào, hãy mở phiên bản dự án chưa được tối ưu hoá trên StackBlitz rồi thử nhập tên nhân viên.
Bạn có thể xác minh rằng sự chậm trễ đến từ hàm fibonacci
bằng cách thiết lập dự án mẫu và mở thẻ Hiệu suất của Công cụ của Chrome cho nhà phát triển.
- Nhấn tổ hợp phím "Control + Shift + J" (hoặc "Command+Option+J" trên máy Mac) để mở Công cụ cho nhà phát triển.
- Nhấp vào thẻ Hiệu suất.
Bây giờ, hãy nhấp vào biểu tượng Record (Ghi) (ở góc trên cùng bên trái của bảng điều khiển Performance (Hiệu suất)) rồi bắt đầu nhập vào một trong các hộp văn bản trong ứng dụng. Sau vài giây, hãy nhấp lại vào biểu tượng Record (Ghi) để dừng ghi. Sau khi Công cụ của Chrome cho nhà phát triển xử lý tất cả dữ liệu phân tích tài nguyên đã thu thập, bạn sẽ thấy nội dung như sau:
Nếu có nhiều nhân viên trong danh sách này, thì quá trình này có thể chặn luồng giao diện người dùng của trình duyệt và gây hiện tượng rớt khung hình, dẫn đến trải nghiệm không tốt cho người dùng.
Bỏ qua các cây con thành phần
Khi người dùng nhập văn bản vào EmployeeListComponent
bán hàng, bạn biết rằng dữ liệu trong bộ phận R&D sẽ không thay đổi, vì vậy, không có lý do gì để chạy tính năng phát hiện thay đổi trên thành phần. Để đảm bảo phiên bản R&D không kích hoạt tính năng phát hiện thay đổi, hãy đặt changeDetectionStrategy
của EmployeeListComponent
thành OnPush
:
import { ChangeDetectionStrategy, ... } from '@angular/core';
@Component({
selector: 'app-employee-list',
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['employee-list.component.css']
})
export class EmployeeListComponent {...}
Giờ đây, khi người dùng nhập văn bản, tính năng phát hiện thay đổi chỉ được kích hoạt cho bộ phận tương ứng:
Bạn có thể tìm thấy cách tối ưu hoá này được áp dụng cho ứng dụng gốc tại đây.
Bạn có thể đọc thêm về chiến lược phát hiện thay đổi OnPush
trong tài liệu chính thức về Angular.
Để xem hiệu quả của hoạt động tối ưu hóa này, hãy nhập một nhân viên mới vào ứng dụng trên StackBlitz.
Sử dụng các ống thuần tuý
Mặc dù chiến lược phát hiện thay đổi cho EmployeeListComponent
hiện được đặt thành OnPush
, nhưng Angular vẫn tính toán lại giá trị số cho tất cả nhân viên trong bộ phận khi người dùng nhập văn bản vào mục nhập tương ứng.
Để cải thiện hành vi này, bạn có thể tận dụng ống dẫn thuần tuý. Cả ống thuần tuý và không thuần tuý đều chấp nhận dữ liệu đầu vào và trả về kết quả có thể dùng trong mẫu. Sự khác biệt giữa hai loại này là đường ống thuần tuý sẽ chỉ tính toán lại kết quả nếu nhận được dữ liệu đầu vào khác với lệnh gọi trước đó.
Hãy nhớ rằng ứng dụng sẽ tính toán một giá trị để hiển thị dựa trên giá trị số của nhân viên, gọi phương thức calculate
được xác định trong EmployeeListComponent
. Nếu bạn di chuyển phép tính sang một dấu sổ thẳng, Angular sẽ chỉ tính toán lại biểu thức dấu sổ thẳng khi đối số của nó thay đổi. Khung này sẽ xác định xem các đối số của pipe có thay đổi hay không bằng cách thực hiện kiểm tra tham chiếu. Điều này có nghĩa là Angular sẽ không thực hiện bất kỳ phép tính toán lại nào trừ khi giá trị số của một nhân viên được cập nhật.
Dưới đây là cách chuyển kết quả tính toán kinh doanh vào hệ thống dấu gạch đứng có tên là 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);
}
}
Phương thức transform
của dấu sổ thẳng gọi hàm fibonacci
. Lưu ý rằng đường ống là thuần tuý. Angular sẽ xem xét tất cả các ống dẫn thuần tuý trừ phi bạn chỉ định khác.
Cuối cùng, hãy cập nhật biểu thức bên trong mẫu cho EmployeeListComponent
:
<mat-chip-list>
<md-chip>
{{ item.num | calculate }}
</md-chip>
</mat-chip-list>
Vậy là xong! Giờ đây, khi người dùng nhập văn bản liên quan đến bất kỳ phòng ban nào, ứng dụng sẽ không tính toán lại giá trị số cho từng nhân viên.
Trong ứng dụng dưới đây, bạn có thể thấy việc nhập văn bản mượt mà hơn bao nhiêu!
Để xem hiệu quả của lần tối ưu hoá gần đây nhất, hãy thử ví dụ này trên StackBlitz.
Bạn có thể xem mã có tính năng tối ưu hoá quy trình thuần tuý của ứng dụng gốc tại đây.
Kết luận
Khi gặp tình trạng làm chậm thời gian chạy trong một ứng dụng Angular:
- Phân tích tài nguyên ứng dụng bằng Công cụ của Chrome cho nhà phát triển để xem nguyên nhân gây ra tình trạng chậm.
- Ra mắt chiến lược phát hiện thay đổi
OnPush
để cắt giảm các cây con của một thành phần. - Di chuyển các phép tính nặng sang các ống thuần tuý để cho phép khung lưu các giá trị đã tính vào bộ nhớ đệm.