Angular 23 的 Zoneless 變更偵測是 Angular 架構的一次重大革新。它移除了對 Zone.js 的依賴,讓變更偵測變得更可預測、更高效。
Zone.js 的歷史包袱
Zone.js 從 Angular 2 開始就是變更偵測的核心:
javascript
// Zone.js 的工作原理
Zone.current.fork({
onInvokeTask: (delegate, zone, target, task, ...args) => {
const result = delegate.invokeTask(target, task, ...args);
ngZone.runOutsideAngular(() => {
ngZone.run(() => {});
});
return result;
}
});
Zone.js 的問題:
- 效能開銷:每個非同步操作都可能觸發變更偵測
- 除錯困難:錯誤堆疊被 Zone.js 包裝
- 不可預測:不知道什麼時候會觸發變更偵測
- 體積膨脹:Zone.js 本身約 15KB
Zoneless 工作原理
Zoneless 模式下,變更偵測由開發者明確控制:
typescript
@Component({
selector: 'app-counter',
template: `
<div>{{ count }}</div>
<button (click)="increment()">+1</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
count = 0;
increment() {
this.count++;
this.cdr.markForCheck();
}
constructor(private cdr: ChangeDetectorRef) {}
}
或使用 Signal:
typescript
@Component({
selector: 'app-counter',
template: `
<div>{{ count() }}</div>
<button (click)="increment()">+1</button>
`
})
export class CounterComponent {
count = signal(0);
increment() {
this.count.update(n => n + 1);
// Signal 自動觸發變更偵測
}
}
從 Zone.js 遷移
遷移是漸進式的:
步驟 1:啟用 Zoneless 模式
typescript
// main.ts
bootstrapApplication(AppComponent, {
providers: [
provideZonelessChangeDetection()
]
});
步驟 2:修復變更偵測
typescript
// 之前:依賴 Zone.js 自動偵測
ngOnInit() {
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}
// 之後:明確標記變更
ngOnInit() {
this.userService.getUsers().subscribe(users => {
this.users = users;
this.cdr.markForCheck();
});
}
步驟 3:使用 Signal(推薦)
typescript
@Component({...})
export class UserListComponent {
users = signal<User[]>([]);
ngOnInit() {
this.userService.getUsers().subscribe(users => {
this.users.set(users);
});
}
}
Zoneless 的優勢
優勢 1:效能提升
基準測試結果:
- 初始渲染:提升 20-30%
- 互動回應:提升 15-25%
- 記憶體佔用:減少 10-20%
優勢 2:除錯更清晰
Zone.js 會包裝錯誤堆疊:
// Zone.js 模式
Error at ZoneDelegate.invokeTask (zone.js:402)
at Zone.runTask (zone.js:175)
at Component.handleClick (component.ts:15) // 真正的錯誤位置
// Zoneless 模式
Error at Component.handleClick (component.ts:15) // 直接定位
Signal 與 Zoneless 的配合
Signal 是 Zoneless 模式的最佳搭檔:
typescript
@Component({
selector: 'app-product-list',
template: `
<div *ngFor="let product of filteredProducts()">
{{ product.name }} - {{ product.price }}
</div>
`
})
export class ProductListComponent {
products = signal<Product[]>([]);
searchTerm = signal('');
filteredProducts = computed(() => {
const term = this.searchTerm();
return this.products().filter(p =>
p.name.toLowerCase().includes(term.toLowerCase())
);
});
}
小結
Angular 23 的 Zoneless 變更偵測讓應用更高效、更可預測。遷移需要漸進式進行,優先使用 Signal 替代手動標記。2026 年的 Angular 開發,Zoneless 不是可選項,而是推薦的最佳實踐。
