Change Detection
Angular的變更檢測是框架核心功能之一,負責確保畫面上顯示的內容與底層資料保持同步。
避免 Zone.js 監控 - runOutsideAngular
runOutsideAngular
這個方法可以讓特定程式碼執行不受到 Zone.js 的監控,有效避免不必要的變更檢測:
private zone = inject(NgZone);
this.zone.runOutsideAngular(() => {
// 這裡的程式碼不會觸發變更檢測
// 適合用於頻繁執行但不需要更新UI的操作,例如:
// - 第三方套件初始化
// - 高頻率事件處理(如滾動、調整大小)
// - 動畫
});
OnPush 變更檢測策略
Angular 提供兩種變更檢測策略:
預設策略 (
ChangeDetectionStrategy.Default
) - 較積極的檢測策略OnPush 策略 (
ChangeDetectionStrategy.OnPush
) - 效能優化策略
如何使用 OnPush
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
@Component({
selector: 'app-messages',
standalone: true,
templateUrl: './messages.component.html',
styleUrl: './messages.component.css',
imports: [MessagesListComponent, NewMessageComponent],
changeDetection: ChangeDetectionStrategy.OnPush, // 關鍵設定
})
export class MessagesComponent {
messages = signal<string[]>([]);
// ...其他程式碼
}
OnPush 變更檢測的觸發條件
使用 OnPush 策略時,變更檢測僅在以下情況觸發:
@Input() 參考改變 - 當輸入屬性的參考變更(而非內部屬性變更)
事件處理 - 如使用者點擊事件等DOM事件
Signal 變更 - Signal 值的改變會觸發
手動觸發 - 使用
ChangeDetectorRef
手動標記Observable 和 async pipe - 使用 async pipe 訂閱 Observable 時有新值發出
使用 Signal 搭配 OnPush
Signal 是 Angular 14+ 引入的反應式狀態管理方式,與 OnPush 策略結合使用效果讚讚:
@Component({
// ...
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MessagesComponent {
messages = signal<string[]>([]);
onAddMessage(message: string) {
// Signal 更新會自動觸發變更檢測,即使使用 OnPush
this.messages.update((oldMessages) => [...oldMessages, message]);
}
}
手動觸發變更檢測
在特定情況下,需要手動觸發變更檢測,尤其是使用外部服務或非 Signal 的資料源:
import { Component, ChangeDetectionStrategy, inject, ChangeDetectorRef } from '@angular/core';
@Component({
// ...
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MessagesListComponent implements OnInit {
private messageService = inject(MessagesService);
private cdRef = inject(ChangeDetectorRef);
messages: string[] = [];
ngOnInit(): void {
this.messageService.message$.subscribe((message) => {
this.messages = message;
this.cdRef.markForCheck(); // 手動標記此組件需要檢查
});
}
}
ChangeDetectorRef 提供的方法:
markForCheck()
- 標記此組件及其祖先需要檢查detectChanges()
- 立即檢查此組件及其子項detach()
- 將組件分離出變更檢測樹reattach()
- 重新附加組件到變更檢測樹
訂閱管理和清理
使用 destroyRef
清理訂閱
destroyRef
清理訂閱ngOnInit(): void {
const subscription = this.messageService.message$.subscribe((message) => {
this.messages = message;
this.cdRef.markForCheck();
});
this.destroyRef.onDestroy(() => {
subscription.unsubscribe();
});
}
使用 takeUntilDestroyed
操作符 (Angular 16+)
takeUntilDestroyed
操作符 (Angular 16+)import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
constructor() {
this.messageService.message$
.pipe(takeUntilDestroyed())
.subscribe((message) => {
this.messages = message;
this.cdRef.markForCheck();
});
}
使用 Async Pipe
Async Pipe 是處理 Observable 最簡潔的方式,它會自動設置訂閱並在組件銷毀時自動清理:
// Component 程式碼
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MessagesListComponent {
messages$ = this.messageService.message$; // 直接使用 Observable
}
<!-- 模板 -->
<ul>
@for (message of messages$ | async; track message) {
<li>{{ message }}</li>
}
</ul>
效能建議
為大多數組件使用 OnPush 策略
對頻繁變更但不影響 UI 的操作使用
runOutsideAngular
優先考慮使用 Signal 或 async pipe 處理資料
避免在模板中使用複雜計算,需要時使用 pure pipes 或 computed signals
組件銷毀時務必清理訂閱,避免記憶體洩漏
Last updated