
在 Oasis Digital,我们接受培训并与使用 Angular 构建软件的大公司合作。这一次,我们学到了很多有关构建性能敏感应用程序的知识。从这些经验中,我们总结出了构建出色用户体验的三大性能技巧。
要讨论任何类型的应用程序性能,定义我们的含义很重要。
负责创建高度响应的界面要求我们关心性能。这意味着在 17 毫秒内响应用户输入和渲染。实现这一点可以创造流畅、无缝的体验,从而增强用户对您的应用程序的信心。
我们经常编写应用程序,很少关心内部 Angular 操作。但是,有些情况需要微调并更深入地了解 Angular 的底层功能。随着数据大小或功能复杂性的增加,通常需要小心。数据表就是一个典型的例子。通常,适用于少量数据的表会随着数据大小的增加而变得滞后。
了解应用程序事件的行为方式以及它们如何与 Angular 交互是提高性能的关键。因此,Angular 中的运行时性能与其变更检测过程相关,该过程包括三个步骤:
让我们看一下可以改进每个方面的一些简单方法。
事件处理程序可以存在于 Angular 应用程序中的许多位置。最明显的例子是 DOM 和组件事件绑定。将这些事件设计为花费尽可能少的时间,可确保更改检测花费的时间不超过 17 毫秒。如果未达到此目标,帧速率将降至每秒 60 帧以下。发生这种情况是因为 Angular 必须等待回调完成才能继续进行更改检测并呈现更新。
让我们看一个简单的例子,其中事件绑定的执行时间可能比预期的要长。以下代码包含一个响应单击事件的组件 (AppComponent)。它通过将值从模板传递到服务 (ListService) 来实现这一点。该服务反过来利用该值来过滤列表。最后,控制权在完成之前返回到单击处理程序。直到控制权从服务返回后,更改检测才能继续。随着列表大小的增加,事件的性能将会下降。
app.component.html
<input type="text" [(ngMode)]="searchTerm">
<button (click)="update()">Search</button>
<ul>
<li *ngFor="let instructor of instructors">
{{ instructor }}
</li>
</ul>
app.component.ts
import { Component } from '@angular/core';
@Component ({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
public instructors = [];
searchTerm = '';
constructor(private ls: ListService) {
this.instructors = ls.getList(this.searchTerm);
}
// Executes when user clicks the "Search" button.
// Execution will flow into getList to perform the search.
// The results arrive back into the update method and assigned
// to the component.
// All of this happens within the same change detection cycle.
update() {
this.instructors = this.ls.getList(this.searchTerm);
}
}
list.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class ListService {
private instructorList = [
'Paul Spears',
'Andrew Wiens',
'John Baur',
'Rachel Noccioli',
'Lance Finney',
];
getList(searchTerm: string) {
// Though this is a simple search or a small data set,
// As the data grows in length and complexity the performance of this method
// will likely degrade.
return this.instructorList
.filter(instructor => {
return instructor === searchTerm || searchTerm === '';
});
}
}
如上所述,很容易忘记事件处理程序通常会运行在组件定义之外定义的代码。识别这些冗长的过程可以使它们得到改进。这通常采取选择更有效的算法的形式。或者,异步执行此过程允许在过滤完成时完成更改检测。
默认情况下,Angular 应用程序中的组件几乎在每次用户交互时都会进行更改检测。但是,Angular 允许您控制这个过程。您可以向 Angular 指示组件子树是否是最新的,并将其从更改检测中排除。
从更改检测中排除组件子树的第一种方法是在 @Component 装饰器中将 `changeDetection` 属性设置为 `ChangeDetectionStrategy.OnPush`。这告诉 Angular,仅需要检查输入是否已更改,并且所有输入都可以被视为不可变。
instructor-list.component.html
<ul>
<li *ngFor="let instructor of instructors">
{{ instructor }}
</li>
</ul>
instructor-list.component.ts
@Component({
selector: 'app-instructor-list',
templateUrl: './instructor-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class InstructorListComponent {
@Input() instructors = [];
}
以增加一点复杂性为代价,可以增加控制量。例如,通过注入 ChangeDetectorRef 服务,您可以通知 Angular 组件应该与更改检测分离。然后,您可以手动控制自己调用“reattach”或“detectChanges()”,从而完全控制检查组件子树的时间和地点。
import {
Component, Input, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectorRef
} from '@angular/core';
@Component({
selector: 'app-instructor-list',
templateUrl: './instructor-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class InstructorListComponent implements AfterViewInit {
// Suppose this time the instructor list doesn't change after it arrives
@Input() instructors = [];
constructor(private cdr: ChangeDetectorRef) { }
// Wait until the view inits before disconnecting
ngAfterViewInit() {
// Since we know the list is not going to change
// let's request that this component not undergo change detection at all
this.cdr.detach();
}
// Angular provides additional controls such as the following
// if the situation allows
// Request a single pass of change detection for the application
// this.cdr.markForCheck();
// Request a single pass of change detection for just this component
// this.cdr.detectChanges();
// Connect this component back to the change detection process
// this.cdr.reattach();
}
默认情况下,当迭代对象列表时,Angular 将使用对象标识来确定是否添加、删除或重新排列项目。这对于大多数情况都很有效。然而,随着不可变实践的引入,列表内容的更改会生成新的对象。反过来,ngFor 将生成要渲染的新 DOM 元素集合。如果列表足够长或足够复杂,这将增加浏览器呈现更新所需的时间。为了缓解这个问题,可以使用 trackBy 来指示如何确定条目的更改。
instructor-list.component.html
<ul>
<li *ngFor="let instructor of instructorList: trackBy: trackByName" >
<span>Instructor Name {{ instructor.name }}</span>
</li>
</ul>
instructor-list.component.ts
import { Component } from '@angular/core';
import { ListService } from '../list.service';
@Component({
selector: 'app-instructor-list',
templateUrl: './instructor-list.component.html'
})
export class InstructorListComponent {
instructorList = {name: string}[];
constructor(private ls: ListService){
// In this example let's assume the list service provides the
// instructors as a realtime list of filtered instructors.
// New updates are sent at regular intervals regardless of content change.
// As a result the object references change with each update.
ls.getList()
.subscribe(list => this.instructorList = list);
}
// Treat the instructor name as the unique identifier for the object
trackByName(index, instructor) {
return instructor.name;
}
}
面对性能问题时知道从哪里开始可能是一项艰巨的任务。希望这些实际示例能够展示性能问题可能隐藏的地方并帮助您入门。尽管这些技巧是一个很好的起点,但还有更多提高性能的机会。您可以在我们完整的Angular 运行时性能指南中阅读有关这些技巧的更多信息。
文档来源地址:https://blog.angular.dev/3-tips-for-angular-runtime-performance-from-the-real-world-d467fbc8f66e