性能优化是Angular应用开发中至关重要的环节。本章将深入讲解各种性能优化技巧,帮助你构建快速、响应灵敏的应用。
将组件的变更检测策略改为OnPush,只有在输入属性发生变化或事件触发时才进行检查。
// 使用OnPush策略
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserProfileComponent {
// 组件代码
}
纯管道只会在输入值发生纯变更时执行变换,避免不必要的重新计算。
// 纯管道示例
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'currencyFormat',
pure: true // 默认就是true,可以省略
})
export class CurrencyFormatPipe implements PipeTransform {
transform(value: number): string {
return `$${value.toFixed(2)}`;
}
}
不要在组件中频繁调用markForCheck或detectChanges。
// 错误示例 - 频繁触发变更检测
export class BadComponent {
constructor(private cdRef: ChangeDetectorRef) {}
updateData() {
// 避免在循环或频繁调用的函数中这样做
setInterval(() => {
this.cdRef.markForCheck(); // 太频繁了!
}, 100);
}
}
// 正确示例 - 使用rxjs的节流
export class GoodComponent {
private data$ = interval(100).pipe(
throttleTime(1000), // 每秒最多更新一次
map(() => this.getNewData())
);
}
将应用拆分为多个模块,按需加载,减少初始包大小。
// 路由配置中的懒加载
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module')
.then(m => m.AdminModule)
},
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module')
.then(m => m.DashboardModule)
},
{
path: 'reports',
loadChildren: () => import('./reports/reports.module')
.then(m => m.ReportsModule)
}
];
使用预加载策略在空闲时加载后续模块。
// 自定义预加载策略
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
export class CustomPreloadStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable): Observable {
if (route.data && route.data['preload']) {
return load();
}
return of(null);
}
}
// 在路由中使用
const routes: Routes = [
{
path: 'settings',
loadChildren: () => import('./settings/settings.module')
.then(m => m.SettingsModule),
data: { preload: true } // 标记为需要预加载
}
];
// 在AppModule中提供策略
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: CustomPreloadStrategy
})],
providers: [CustomPreloadStrategy]
})
初始加载时间对比(数值越小越好)
使用生产环境构建配置,启用各种优化。
# 生产环境构建命令
ng build --prod
# 更详细的构建配置
ng build --prod --aot --build-optimizer --optimization
使用动态导入进行代码分割。
// 动态导入组件
@Component({
template: `<ng-container *ngComponentOutlet="currentComponent"></ng-container>`
})
export class LazyComponentLoader {
currentComponent: any;
async loadComponent(componentName: string) {
switch(componentName) {
case 'chart':
const { ChartComponent } = await import('./chart/chart.component');
this.currentComponent = ChartComponent;
break;
case 'table':
const { TableComponent } = await import('./table/table.component');
this.currentComponent = TableComponent;
break;
}
}
}
使用Tree Shaking移除未使用的代码。
// angular.json配置
{
"projects": {
"my-app": {
"architect": {
"build": {
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
}
}
}
}
}
避免内存泄漏,及时取消rxjs订阅。
// 使用takeUntil模式取消订阅
export class SafeComponent implements OnDestroy {
private destroy$ = new Subject();
constructor() {
// 自动在组件销毁时取消订阅
interval(1000)
.pipe(takeUntil(this.destroy$))
.subscribe(value => {
console.log(value);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
// 使用async管道(推荐)
export class AsyncPipeComponent {
data$ = this.dataService.getData().pipe(
shareReplay(1) // 缓存结果
);
}
// 在模板中使用
// {{ data$ | async }}
在ngFor中使用trackBy函数,避免不必要的DOM操作。
<!-- 使用trackBy函数 -->
<div *ngFor="let item of items; trackBy: trackByFn">
{{item.name}}
</div>
// 组件中的trackBy函数
trackByFn(index: number, item: any): number {
return item.id; // 使用唯一标识符
}
// 错误示例 - 未取消订阅
export class LeakyComponent {
constructor() {
// 错误:未保存订阅引用
interval(1000).subscribe(value => {
console.log(value);
});
// 错误:未清理定时器
setInterval(() => {
this.update();
}, 1000);
}
}
// 正确示例
export class CleanComponent implements OnDestroy {
private subscription: Subscription;
private intervalId: any;
constructor() {
// 保存订阅引用
this.subscription = interval(1000).subscribe(value => {
console.log(value);
});
// 保存定时器ID
this.intervalId = setInterval(() => {
this.update();
}, 1000);
}
ngOnDestroy() {
// 清理所有资源
this.subscription.unsubscribe();
clearInterval(this.intervalId);
}
}
对于大型列表,使用虚拟滚动减少DOM节点数量。
<!-- 使用CDK虚拟滚动 -->
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
<div *cdkVirtualFor="let item of items" class="item">
{{item.name}}
</div>
</cdk-virtual-scroll-viewport>
// 安装CDK
ng add @angular/cdk
// 在模块中导入
import { ScrollingModule } from '@angular/cdk/scrolling';
@NgModule({
imports: [ScrollingModule]
})
将CPU密集型任务移入Web Worker,避免阻塞UI线程。
// 创建Web Worker
ng generate web-worker app
// 在组件中使用
export class WorkerComponent {
constructor() {
if (typeof Worker !== 'undefined') {
const worker = new Worker('./app.worker', { type: 'module' });
worker.onmessage = ({ data }) => {
console.log('收到worker消息:', data);
};
worker.postMessage('开始计算');
}
}
}
延迟加载图片,优化图片资源。
<!-- 使用懒加载图片 -->
<img [src]="imageSrc" loading="lazy" alt="描述文本">
<!-- 响应式图片 -->
<img [srcset]="imageSrcSet" sizes="(max-width: 600px) 100vw, 50vw">
<!-- 使用picture元素 -->
<picture>
<source media="(max-width: 768px)" [srcset]="mobileImage">
<source media="(min-width: 769px)" [srcset]="desktopImage">
<img [src]="fallbackImage" alt="描述">
</picture>
使用Angular Universal实现服务端渲染,提升首屏加载速度。
# 添加Universal支持
ng add @nguniversal/express-engine
// 服务端渲染注意事项
export class AppComponent {
// 避免在服务端运行浏览器特定代码
constructor(@Inject(PLATFORM_ID) private platformId: Object) {
if (isPlatformBrowser(this.platformId)) {
// 只在浏览器中执行的代码
this.initBrowserSpecificFeatures();
}
if (isPlatformServer(this.platformId)) {
// 只在服务器中执行的代码
this.initServerSpecificFeatures();
}
}
}
安装Angular DevTools扩展,分析变更检测和性能。
// 性能测量代码示例
import { enableProdMode } from '@angular/core';
// 在生产环境中启用性能模式
if (environment.production) {
enableProdMode();
}
// 使用performance API测量
export class PerformanceService {
measureOperation(name: string, operation: () => void) {
const start = performance.now();
operation();
const end = performance.now();
console.log(`${name} 耗时: ${end - start}ms`);
}
}
| 优化技巧 | 收益 | 复杂度 | 推荐指数 |
|---|---|---|---|
| OnPush变更检测 | 高 | 低 | ★★★★★ |
| 模块懒加载 | 高 | 中 | ★★★★★ |
| 取消订阅管理 | 高 | 低 | ★★★★★ |
| 虚拟滚动 | 极高 | 中 | ★★★★☆ |
| 服务端渲染 | 极高 | 高 | ★★★☆☆ |
| Web Workers | 中 | 高 | ★★☆☆☆ |