Angular 性能优化全攻略
在 Angular 开发中,性能优化是一个至关重要的话题。本文将介绍几种实用的 Angular 性能优化方法,包括使用
trackBy
函数优化列表渲染、将大量计算迁移到纯管道以及使用 Web Workers 处理大量计算,还会涉及使用性能预算进行审计。
1. 使用 trackBy 函数优化列表渲染
1.1 问题描述
在 Angular 中,
*ngFor
指令默认将对象本身视为其唯一标识。如果只是更改了
*ngFor
指令中对象的某个属性,它不会重新渲染该对象的模板;但如果提供了一个新的对象(内存中的不同引用),则会重新渲染该特定项的内容。
1.2 示例代码
在
data.service.ts
文件中,
updateUser()
方法的代码如下:
updateUser(updatedUser: AppUserCard) {
this.users = this.users.map((user) => {
if (user.email === updatedUser.email) {
return {
...updatedUser,
};
}
// this tells angular that every object has now
a different reference
return { ...user };
});
}
这里使用了对象展开运算符
{ … }
为数组中的每个项返回一个新对象,这会告诉
*ngFor
指令重新渲染
TheAmazingListComponent
类中
listItems
数组的每个项的 UI。
1.3 潜在问题
假设向服务器发送查询以查找或过滤用户,服务器可能返回包含 100 个用户的响应。其中约 90 个已经在视图中渲染,只有 10 个不同。但由于以下潜在原因(不限于这些),Angular 会重新渲染所有列表项:
- 用户的排序/位置可能发生了变化。
- 用户的数量可能发生了变化。
1.4 解决方案
我们可以使用
trackBy
函数来避免将对象引用用作每个列表项的唯一标识符。对于我们的用例,每个用户的电子邮件是唯一的,因此可以使用
trackBy
函数告诉 Angular 使用用户的电子邮件作为唯一标识符。这样,即使在
updateUser()
方法更新用户后返回新对象,Angular 也不会重新渲染所有列表项,因为新对象(用户)具有相同的电子邮件,Angular 会使用它来跟踪它们。
1.5 总结
使用
trackBy
函数可以显著优化 Angular 中列表的渲染性能,避免不必要的重新渲染。
2. 将大量计算迁移到纯管道
2.1 准备工作
要进行此优化,需要完成以下准备步骤:
1. 打开项目:在 VS Code 中打开项目
Chapter12/start_here/using-pure-pipes
。
2. 安装依赖:在终端中运行
npm install
安装项目依赖。
3. 启动应用:运行
ng serve -o
命令启动 Angular 应用并在浏览器中打开,应用将运行在
http://localhost:4200
。
2.2 问题描述
当前应用存在性能问题,特别是
UserCardComponent
类,它使用
idUsingFactorial()
方法生成唯一 ID 显示在卡片上。当在搜索框中输入内容时,应用会卡顿,无法立即看到输入的字母,搜索结果显示也需要一段时间。
2.3 解决方案步骤
- 创建 Angular 管道 :在项目根目录的终端中运行以下命令创建管道:
ng g pipe core/pipes/unique-id
-
复制并修改代码
:将
user-card.component.ts文件中的createUniqueId()方法代码复制到unique-id.pipe.ts文件中,并进行适当修改,代码如下:
@Pipe({...})
export class UniqueIdPipe implements PipeTransform {
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef
ghijklmnopqrstuvwxyz0123456789';
createUniqueId(length) {
var result = '';
const charactersLength = this.characters.length;
for (let i = 0; i < length; i++) {
result += this.characters.charAt(
Math.floor(Math.random() * charactersLength)
);
}
return result;
}
...
transform(index: unknown, ...args: unknown[]): unknown {
return null;
}
}
-
复制并更新方法
:将
user-card.component.ts文件中的idUsingFactorial()方法复制到unique-id.pipe.ts文件中并更新,代码如下:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'uniqueId',
})
export class UniqueIdPipe implements PipeTransform {
...
idUsingFactorial(num, length = 1) {
if (num === 1) {
return this.createUniqueId(length);
} else {
const fact = length * (num - 1);
return this.idUsingFactorial(num - 1, fact);
}
}
transform(index: number): string {
return this.idUsingFactorial(index);
}
}
-
更新 HTML 文件
:更新
user-card.component.html文件以使用uniqueId管道,代码如下:
<div class="user-card">
<div class="card" *ngIf="user" (click)="cardClicked()">
<img [src]="user.picture.large" class="card-img-top"
alt="..." />
<div class="card-body">
<h5 class="card-title">{{ user.name.first }}
{{ user.name.last }}</h5>
<p class="card-text">{{ user.email }}</p>
<p class="card-text unique-id" title="{{ index |
uniqueId }}">
{{ index | uniqueId }}
</p>
<a href="tel: {{ user.phone }}" class="btn
btn-primary">{{
user.phone
}}</a>
</div>
</div>
</div>
-
验证优化效果
:刷新应用,在搜索框中输入
Elfie Siegert,可以发现 UI 不再阻塞,能够立即看到输入的字母,搜索结果也更快。
2.4 工作原理
Angular 默认在应用中触发的每个浏览器事件上运行变更检测。由于在组件模板(UI)中使用了
idUsingFactorial()
方法,每次 Angular 运行变更检测机制时,该函数都会运行,导致更多的计算和性能问题。而纯管道是纯函数,除非输入发生变化,否则不会触发变更检测。将大量计算迁移到纯管道后,当在搜索框中输入内容时,索引值不变,不会触发变更检测,从而避免了不必要的计算,优化了性能并解锁了 UI 线程。
2.5 总结
将大量计算迁移到纯管道是一种有效的 Angular 性能优化方法,可以避免不必要的计算,提高应用的响应速度。
3. 使用 Web Workers 处理大量计算
3.1 准备工作
要进行此优化,需要完成以下准备步骤:
1. 打开项目:在 VS Code 中打开项目
Chapter12/start_here/using-web-workers
。
2. 安装依赖:在终端中运行
npm install
安装项目依赖。
3. 启动应用:运行
ng serve -o
命令启动 Angular 应用并在浏览器中打开,应用将运行在
http://localhost:4200
。
3.2 问题描述
打开应用后,会发现用户卡片的渲染需要一些时间,这表明在计算完成之前 UI 线程被阻塞。问题出在
UserService
类中的
saveUserUniqueIdsToStorage()
方法,它在将唯一 ID 保存到
localStorage
之前会循环数千次,导致应用卡顿。
3.3 解决方案步骤
- 创建 Web Worker :在项目根目录的终端中运行以下命令创建 Web Worker:
ng generate web-worker core/workers/idGenerator
-
复制代码到 Worker 文件
:将
UserService类中saveUserUniqueIdsToStorage()方法的for循环复制到新创建的id-generator.worker.ts文件中,代码如下:
/// <reference lib="webworker" />
import createUniqueId from '../constants/create-
unique-id';
addEventListener('message', ({ data }) => {
console.log('message received IN worker', data);
const { index, email } = data;
let uniqueId;
for (let i = 0, len = (index + 1) * 100000; i < len;
++i) {
uniqueId = createUniqueId(50);
}
postMessage({ uniqueId, email });
});
-
创建 Worker 实例
:在
constants文件夹中创建一个新文件get-unique-id-worker.ts,并添加以下代码:
let UNIQUE_ID_WORKER: Worker = null;
const getUniqueIdWorker = (): Worker => {
if (typeof Worker !== 'undefined' && UNIQUE_ID_WORKER
=== null) {
UNIQUE_ID_WORKER = new Worker('../workers/
id-generator.worker', {
type: 'module',
});
}
return UNIQUE_ID_WORKER;
};
export default getUniqueIdWorker;
-
在服务中使用 Worker
:更新
user.service.ts文件,使用 Worker,代码如下:
...
import getUniqueIdWorker from '../constants/get-unique-
id-worker';
@Injectable({...})
export class UserService {
...
worker: Worker = getUniqueIdWorker();
constructor(private http: HttpClient) {
this.worker.onmessage = ({ data: { uniqueId, email }
}) => {
console.log('received message from worker',
uniqueId, email);
const user = this.usersCache.find((user) => user.
email === email);
localStorage.setItem(
`ng_user__${user.email}`,
JSON.stringify({
...user,
uniqueId,
})
);
};
}
...
}
-
修改保存方法
:再次更新
user.service.ts文件,修改saveUserUniqueIdsToStorage()方法,使用 Worker,代码如下:
...
@Injectable({...})
export class UserService {
...
saveUserUniqueIdsToStorage(user: IUser, index) {
let uniqueId;
const worker: Worker = getUniqueIdWorker();
if (worker !== null) {
worker.postMessage({ index, email: user.email });
} else {
// fallback
for(let i = 0, len = (index + 1) * 100000; i<len;
++i) {
uniqueId = createUniqueId(50);
}
localStorage.setItem(...);
}
}
...
}
- 验证优化效果 :刷新应用,会发现用户卡片的渲染速度比之前快很多,并且可以看到反映应用与 Web Worker 之间通信的日志。
3.4 工作原理
Web Workers 允许我们在与主 JavaScript(或 UI 线程)分开的线程中运行和执行代码。在开始时,每次刷新应用或搜索用户时,UI 线程会被阻塞,直到为每个卡片生成唯一 ID。通过创建 Web Worker,我们将大量计算移到了后台线程。Web Worker 文件使用
addEventListener()
方法监听来自 UI 线程的消息,接收用户卡片的索引和用户的电子邮件,使用
for
循环生成唯一 ID,然后使用
postMessage()
方法将唯一 ID 和电子邮件发送回 UI 线程。在
UserService
类中,监听来自 Worker 的消息,根据收到的电子邮件找到相应的用户,并将用户数据和唯一 ID 存储到
localStorage
中。
3.5 总结
使用 Web Workers 处理大量计算可以避免 UI 线程被阻塞,提高应用的响应速度和用户体验。
4. 使用性能预算进行审计
4.1 准备工作
要进行此优化,需要完成以下准备步骤:
1. 打开项目:在 VS Code 中打开项目
Chapter12/start_here/angular-performance-budget
。
2. 安装依赖:在终端中运行
npm install
安装项目依赖。
3. 构建应用:运行
ng build --configuration production
命令以生产模式构建 Angular 应用,注意控制台的输出,此时
main.*.js
文件的包大小约为 260 千字节(KB)。
4.2 问题描述
当前应用的包大小较小,但随着业务需求的增加,可能会变得很大。为了确保包大小不超过一定限制,可以使用性能预算。
4.3 解决方案步骤
-
打开
app.component.ts文件并更新,代码如下:
...
import * as moment from '../lib/moment';
4.4 总结
使用性能预算进行审计可以帮助我们控制 Angular 应用的包大小,确保应用在不同环境下都能保持良好的性能。
综上所述,通过使用
trackBy
函数优化列表渲染、将大量计算迁移到纯管道、使用 Web Workers 处理大量计算以及使用性能预算进行审计,我们可以显著提高 Angular 应用的性能,为用户提供更好的体验。
4. 使用性能预算进行审计(续)
4.3 解决方案步骤(续)
在完成上述
app.component.ts
文件的更新后,我们可以进一步设置性能预算。具体步骤如下:
1.
打开
angular.json
文件
:该文件是 Angular 项目的配置文件,我们将在其中设置性能预算。
2.
添加预算配置
:在
angular.json
文件中找到
projects
->
<your-project-name>
->
architect
->
build
节点,添加
budgets
配置项,示例如下:
{
"projects": {
"<your-project-name>": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
// 其他配置项...
"budgets": [
{
"type": "bundle",
"name": "main",
"maximumWarning": "250kb",
"maximumError": "300kb"
}
]
}
}
}
}
}
}
上述配置表示,当
main
包的大小超过 250kb 时,会给出警告;超过 300kb 时,构建会报错。
-
重新构建应用
:再次运行
ng build --configuration production命令,此时如果包大小超过了我们设置的预算,控制台会输出相应的警告或错误信息。
4.4 工作原理
性能预算的工作原理是在构建过程中,Angular CLI 会检查各个包的大小,并与我们在
angular.json
中设置的预算进行比较。如果超过了预算,就会根据设置的警告或错误阈值给出相应的提示。通过这种方式,我们可以及时发现包大小的问题,并采取相应的措施进行优化,如移除不必要的依赖、压缩代码等。
4.5 总结
使用性能预算进行审计是一种有效的控制 Angular 应用包大小的方法。通过合理设置预算,我们可以确保应用在不同环境下都能保持良好的性能,避免因包过大导致的加载缓慢等问题。
总结与对比
为了更清晰地展示上述几种性能优化方法的特点和适用场景,我们可以通过以下表格进行对比:
| 优化方法 | 适用场景 | 主要优势 | 实现步骤 |
| — | — | — | — |
| 使用
trackBy
函数 | 列表渲染,避免不必要的重新渲染 | 显著提高列表渲染性能 | 1. 确定唯一标识符;2. 在
*ngFor
中使用
trackBy
函数 |
| 将大量计算迁移到纯管道 | 组件中有大量计算,导致性能问题 | 避免不必要的计算,提高应用响应速度 | 1. 创建管道;2. 复制计算逻辑到管道;3. 在模板中使用管道 |
| 使用 Web Workers 处理大量计算 | 有大量计算任务,阻塞 UI 线程 | 避免 UI 线程被阻塞,提高用户体验 | 1. 创建 Web Worker;2. 复制计算逻辑到 Worker;3. 在服务中使用 Worker |
| 使用性能预算进行审计 | 控制应用包大小,确保性能 | 及时发现包大小问题,便于优化 | 1. 在
angular.json
中设置预算;2. 构建应用,检查预算情况 |
优化流程总结
为了帮助大家更好地理解和应用这些优化方法,下面是一个简单的优化流程 mermaid 流程图:
graph LR
A[发现性能问题] --> B{问题类型}
B --> |列表渲染问题| C[使用 trackBy 函数]
B --> |大量计算问题| D{计算位置}
D --> |组件内| E[将大量计算迁移到纯管道]
D --> |服务内| F[使用 Web Workers 处理大量计算]
B --> |包大小问题| G[使用性能预算进行审计]
C --> H[验证优化效果]
E --> H
F --> H
G --> H
H --> I{是否满足性能要求}
I --> |是| J[结束优化]
I --> |否| A
注意事项
在使用这些优化方法时,还需要注意以下几点:
1.
trackBy
函数
:确保选择的唯一标识符是真正唯一的,否则可能会导致渲染错误。
2.
纯管道
:纯管道的输入必须是不可变的,否则可能会导致不必要的变更检测。
3.
Web Workers
:Web Workers 与主线程之间的通信会有一定的开销,因此要合理安排计算任务,避免频繁通信。
4.
性能预算
:设置预算时要根据实际情况进行合理调整,避免设置过于严格或宽松的预算。
通过以上几种性能优化方法的综合应用,我们可以显著提高 Angular 应用的性能,为用户提供更加流畅、快速的体验。在实际开发中,要根据具体的应用场景和性能问题,选择合适的优化方法,并不断验证和调整,以达到最佳的优化效果。
超级会员免费看

272

被折叠的 条评论
为什么被折叠?



