ゾーンレス変更検出: さようなら Zone.js
Angular 18 では、チームはアプリケーションを実行する実験的な機能を導入しました。 Zone.js なし、Angular 21 ではこの機能が安定しており、 生産の準備ができています。 Zone.js の削除は変更の 1 つを表します。 フレームワークの歴史の中で最も重要なアーキテクチャ上のもので、直接的な影響を及ぼします。 パフォーマンス, バンドルサイズ e メンタルモデルの単純さ.
このシリーズの 2 番目の記事では、 モダンアンギュラー 詳しく見ていきます Zone.js とは何か、それが約 10 年間フレームワークの主力であり続けている理由、およびその方法 に基づく新しいゾーンレス モデル 信号 最終的には余計なものになります。 構成から移行まで、実際のベンチマークと実践的なパターンを通じて、 このガイドには、飛躍するために必要なすべてが記載されています。
最新の Angular シリーズの概要
| # | アイテム | 集中 |
|---|---|---|
| 1 | 角度信号 | きめ細かい応答性 |
| 2 | 現在位置 - ゾーンレス変更検出 | Zone.jsを削除する |
| 3 | 新しいテンプレート @if、@for、@defer | 最新の制御フロー |
| 4 | スタンドアロンコンポーネント | NgModule を使用しないアーキテクチャ |
| 5 | 信号形式 | シグナルを使用したレスポンシブフォーム |
| 6 | SSR と増分水分補給 | サーバーサイドレンダリング |
| 7 | Angular におけるコア Web バイタル | パフォーマンスと指標 |
| 8 | 角度のある PWA | プログレッシブ Web アプリ |
| 9 | 高度な依存関係の注入 | DIツリーシェイク可能 |
| 10 | Angular 17 から 21 への移行 | 移行ガイド |
Zone.js とは何ですか、なぜそれが問題になるのですか
ゾーン.js Angular がバージョン 2 から使用しているライブラリです。 変化を検出するための基本的なメカニズム。そのタスクは理論的には単純ですが、 実際には侵襲的: すべての非同期ブラウザ操作をインターセプトする ビューをいつ更新する必要があるかを確認します。
グローバルなモンキーパッチング
Zone.js は、と呼ばれる手法を通じて機能します。 モンキーパッチ: ロード時
アプリケーションの非同期 API のブラウザーのネイティブ実装を置き換えます。
独自の「ラップされた」バージョンを使用します。これはつまり、 すべて順調です に電話する
setTimeout, setInterval, Promise,
addEventListener, XMLHttpRequest, fetch,
requestAnimationFrame 他の多くの API がインターセプトされます。
// Questo è ciò che Zone.js fa internamente (semplificato)
const originalSetTimeout = window.setTimeout;
window.setTimeout = function(callback: Function, delay: number) {
// Notifica la zona corrente che sta iniziando un task asincrono
const zone = Zone.current;
zone.onScheduleTask('setTimeout');
return originalSetTimeout(() => {
zone.onInvokeTask('setTimeout');
callback();
// Dopo l'esecuzione, Angular sa che qualcosa potrebbe essere cambiato
zone.onHasTask(); // Trigger change detection!
}, delay);
};
// Lo stesso avviene per Promise, addEventListener,
// fetch, XMLHttpRequest, MutationObserver, ecc.
Zone.js の本当の問題
- バンドルサイズ: Zone.js は約を追加します 100KB以上(非圧縮) (約 13KB gzip) をアプリケーションバンドルに追加
- 実行時のオーバーヘッド: 各非同期操作は、アプリケーションの状態を変更しない場合でも、追加のレイヤーを通過します。
- 過剰な変化の検出: シンプルなもの
setTimeoutアニメーションに使用すると、変更検出の全サイクルがトリガーされます。 みんな コンポーネント - ネイティブ API との非互換性: ウェブコンポーネント、
async/awaitネイティブ、queueMicrotaskまた、一部のサードパーティ ライブラリは Zone.js で適切に動作しません。 - 汚染されたデバッグ: スタック トレースには数十の内部 Zone.js フレームが含まれているため、実行フローを追跡することはほぼ不可能です
- ツリーシェイキング不可: アプリが使わなくても
setTimeoutただし、Zone.js はすべての API にパッチを適用します
影響を視覚化するために、1 つのコンポーネントを含むアプリケーションの例を考えてみましょう。
これは、1 秒ごとに更新される時計を示しています。 Zone.js を使用すると、 setInterval
で変更検出サイクルをトリガーします。コンポーネントツリー全体
テンプレート内の 1 つのバインディングのみを更新する必要がある場合でも、ティックごとに更新されます。
従来の変更検出の仕組み
ゾーンレス モデルを理解するには、変更検出がどのように機能するかを理解することが不可欠です 従来の Zone.js ベース。このプロセスはいくつかのステップに分かれています。
Zone.js を使用した変更検出ループ
非同期イベントがインターセプトされると、Zone.js は Angular に通知します。
NgZone、今度は誰が電話しますか ApplicationRef.tick()。
このメソッドは、 ダーティチェック ツリー全体のトップダウン
コンポーネントの。
// 1. L'utente clicca un bottone
// Zone.js intercetta addEventListener
//
// 2. Il callback viene eseguito dentro la NgZone
// NgZone.onMicrotaskEmpty$.emit()
//
// 3. ApplicationRef ascolta e trigghera il tick
class ApplicationRef {
tick(): void {
// Controlla OGNI componente nell'albero
for (const view of this._views) {
view.detectChanges(); // Dirty checking top-down
}
}
}
// 4. Ogni componente confronta i valori correnti con quelli precedenti
class ComponentView {
detectChanges(): void {
// Per OGNI binding nel template:
// if (currentValue !== previousValue) {
// updateDOM();
// }
// Poi controlla tutti i componenti figli...
}
}
Zone.js とゾーンレス フローの比較
| 段階 | Zone.js を使用する | ゾーンレス (信号) |
|---|---|---|
| イベントインターセプト | Zone.js が API にパッチを適用する | インターセプトなし |
| フレームワークに通知する | NgZone がイベントを発行する | シグナルは消費者に通知します |
| チェックの範囲 | コンポーネントツリー全体 | シグナルを読み取るコンポーネントのみ |
| 戦略 | ダーティチェック(値比較) | プッシュベース(直接通知) |
| 制御されたコンポーネント | すべて (または OnPush までのすべて) | 「汚れ」マークのあるもののみ |
| 役に立たないトリガー | 頻繁 (非同期操作ごと) | ゼロ (実際の変更時のみ) |
根本的な問題は明らかです。Zone.js では、Angular はそれを知りません。 cosa 変わりました、 彼はそれを知っているだけです 何かがあるかもしれない 変えられる。したがって、彼はすべてをチェックする必要があります。 シグナルを使用すると、Angular は認識します その通り どの値が変更されたのか、またどの値が変更されたのか コンポーネントはそれに依存します。
新しいゾーンレス モデル
ゾーンレス モデルでは、方程式から Zone.js が完全に排除されます。代わりに、Angular
私を使ってください 信号 いつ、どこでかを知るための主要なメカニズムとして
DOM を更新します。 Angular 18 で実験的に導入されました。
provideExperimentalZonelessChangeDetection()、安定してきました
Angular 21 の公式 provideZonelessChangeDetection().
信号が変化の検出を促進する仕組み
ゾーンレス モデルでは、リフレッシュ サイクルの動作がまったく異なります。 シグナルが値を変更すると、コンポーネントが変更されたことを Angular に自動的に通知します。 テンプレート内でそれを読み取るものは「ダーティ」であり、レビューする必要があります。まさにそのコンポーネント (必要に応じてその子孫も) チェックされます。
// Con Zoneless, i Signals notificano direttamente Angular
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>Contatore: {{ count() }}</p>
<button (click)="increment()">+1</button>
`
})
export class CounterComponent {
count = signal(0);
increment() {
this.count.update(v => v + 1);
// 1. Il Signal sa che il suo valore è cambiato
// 2. Angular marca SOLO questo componente come dirty
// 3. Al prossimo tick, SOLO questo componente viene controllato
// 4. Il binding {{ count() }} viene aggiornato nel DOM
// Nessun altro componente viene toccato!
}
}
ゾーンレスモードでの変化検出トリガー
Zone.js を使用しない場合、Angular は次のメカニズムを通じて変更を検出します。
- 信号の変化: テンプレートに読み取られた信号に変更を加えると、コンポーネントがダーティとしてマークされます。
- イベントバインディング: テンプレート内のイベント ハンドラー (例:
(click)) 実行後にチェックを自動的にトリガーします - 非同期パイプ: 非同期パイプは内部で呼び出しているため、動作し続けます。
markForCheck() - ChangeDetectorRef: 次のように手動でチェックを強制できます
markForCheck()odetectChanges() - テンプレート入力バインディング: バインディングの変更
[input]自動的に追跡されます
ゾーンレスアプリケーションの構成
ゾーンレス アプリケーションに切り替えるには、いくつかの正確な手順が必要です。見てみましょう 一つ一つ。
ステップ 1: ゾーンレスプロバイダーを有効にする
最初のステップはプロバイダーを追加することです provideZonelessChangeDetection()
アプリケーション構成で。
// app.config.ts
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
// Sostituisce provideZoneChangeDetection()
provideZonelessChangeDetection(),
provideRouter(routes),
provideHttpClient(),
]
};
ステップ 2: ポリフィルから Zone.js を削除する
Zone.js は通常、ポリフィルとしてロードされます。ファイルから削除する必要があります
angular.json (o project.json Nxの場合)。
{
"projects": {
"my-app": {
"architect": {
"build": {
"options": {
"polyfills": [
// RIMUOVI questa riga:
// "zone.js"
]
}
},
"test": {
"options": {
"polyfills": [
// RIMUOVI anche qui:
// "zone.js",
// "zone.js/testing"
]
}
}
}
}
}
}
ステップ 3: Zone.js パッケージをアンインストールする
# Rimuovi zone.js dalle dipendenze
npm uninstall zone.js
# Verifica che non ci siano import residui
grep -r "zone.js" src/ --include="*.ts"
# Se trovi import come questi, rimuovili:
# import 'zone.js';
# import 'zone.js/testing';
重要事項を確認してください
Zone.js を削除した後、次の場所に参照が残っていないことを確認します。
src/polyfills.ts(プロジェクトにまだ存在する場合)src/main.ts- すべてのzone.jsインポートを削除しますsrc/test.tsosrc/test-setup.ts- インポートする単体テスト ファイル
zone.js/testing
ステップ 4: ブートストラップを更新する (必要な場合)
// main.ts - Nessun cambiamento necessario nella maggior parte dei casi
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch(err => console.error(err));
// La differenza sta tutta in appConfig:
// provideZonelessChangeDetection() al posto di
// provideZoneChangeDetection()
信号ベースの詳細な変化検出
ゾーンレス モデルでは、シグナルが変更検出の中心となります。 Angular が依存関係を追跡し、更新をスケジュールする方法を理解する 効率的なコードを書くために不可欠です。
依存関係グラフ
Angular が次の内容を含むテンプレートをレンダリングするとき {{ mySignal() }}、
自動的に 1 つを記録します 依存 そのバインディングとシグナルの間。
シグナルが変化すると、フレームワークはどのビューを更新する必要があるかを正確に認識します。
@Component({
selector: 'app-user-profile',
standalone: true,
// ZONELESS: Angular traccia che questo template
// dipende da firstName(), lastName() e fullAddress()
template: `
<h2>{{ firstName() }} {{ lastName() }}</h2>
<p>{{ fullAddress() }}</p>
<button (click)="updateName('Marco')">Cambia nome</button>
`
})
export class UserProfileComponent {
firstName = signal('Federico');
lastName = signal('Calo');
city = signal('Bari');
street = signal('Via Roma 1');
// computed: dipende da city() e street()
fullAddress = computed(() =>
`${this.street()}, ${this.city()}`
);
updateName(name: string) {
this.firstName.set(name);
// Solo il binding {{ firstName() }} viene aggiornato
// lastName() e fullAddress() restano invariati
// Nessun altro componente nell'albero viene controllato
}
}
オンプッシュ自動
ゾーンレス モードでは、すべてのコンポーネントは暗黙的に、あたかもゾーンレス モードであるかのように動作します。
ChangeDetectionStrategy.OnPush。これは、コンポーネントが来ることを意味します
次の場合にのみチェックされます。
- テンプレートで読み取られた信号は値を変更します
- 入力バインディングは新しい値を受け取ります
- テンプレート内のイベントが処理される
- 手動で呼び出されます
markForCheck()
既存の変更検出戦略への影響
| 戦略 | Zone.js を使用する | ゾーンレス |
|---|---|---|
Default |
すべてのティックを常にチェックする | OnPush と同等 (ダーティかどうかのみチェック) |
OnPush |
入力変更、イベント、markForCheck のみをチェックする | 同一の動作 |
markForCheck() |
手動の非同期更新に必要 | 非信号コードにのみ必要 |
Zone.js を使用しない非同期コードの処理
Zone.js が非同期操作を自動的にインターセプトしない場合は、次のことを確認する必要があります。 状態が変化すると Angular に通知されること。シグナルを使用すると、これが起こります 自動的に。ただし、レガシーコードや特殊なケースの場合は、介入が必要になる場合があります。 手動で。
ケース 1: setTimeout と setInterval
@Component({
selector: 'app-timer',
standalone: true,
template: `
<p>Con Signal: {{ countSignal() }}</p>
<p>Senza Signal: {{ countPlain }}</p>
`
})
export class TimerComponent {
// FUNZIONA: Il Signal notifica Angular automaticamente
countSignal = signal(0);
// NON FUNZIONA senza Zone.js: Angular non sa che è cambiato!
countPlain = 0;
private cdr = inject(ChangeDetectorRef);
constructor() {
// Approccio Signal (consigliato)
setInterval(() => {
this.countSignal.update(v => v + 1);
// Angular aggiorna automaticamente il template
}, 1000);
// Approccio senza Signal (richiede intervento manuale)
setInterval(() => {
this.countPlain++;
this.cdr.markForCheck(); // Necessario!
// Senza markForCheck, il template non si aggiorna
}, 1000);
}
}
ケース 2: HTTP 呼び出し
@Component({
selector: 'app-data-loader',
standalone: true,
template: `
@if (loading()) {
<div class="spinner">Caricamento...</div>
} @else {
<ul>
@for (user of users(); track user.id) {
<li>{{ user.name }}</li>
}
</ul>
}
`
})
export class DataLoaderComponent {
private http = inject(HttpClient);
users = signal<User[]>([]);
loading = signal(true);
constructor() {
this.http.get<User[]>('/api/users').subscribe({
next: (data) => {
this.users.set(data); // Signal notifica Angular
this.loading.set(false); // Signal notifica Angular
// Nessun markForCheck necessario!
},
error: (err) => {
console.error('Errore:', err);
this.loading.set(false);
}
});
}
}
ケース 3: WebSocket と連続ストリーム
@Injectable({ providedIn: 'root' })
export class WebSocketService {
private messages = signal<Message[]>([]);
readonly latestMessages = this.messages.asReadonly();
connect(url: string): void {
const ws = new WebSocket(url);
ws.onmessage = (event) => {
const msg: Message = JSON.parse(event.data);
// Il Signal notifica tutti i componenti che lo leggono
this.messages.update(msgs => [...msgs, msg]);
};
}
}
// Nel componente, basta leggere il Signal nel template:
@Component({
template: `
@for (msg of wsService.latestMessages(); track msg.id) {
<div class="message">{{ msg.text }}</div>
}
`
})
export class ChatComponent {
wsService = inject(WebSocketService);
}
ゾーンレス非同期コードの黄金律
ルールは簡単です: Signal 経由でステータスを更新すると、Angular はそれを認識します。
自動的に。単純な (信号ではない) プロパティを更新する場合は、次を呼び出す必要があります。
手動で ChangeDetectorRef.markForCheck()。最良の解決策は
すべてのリアクティブ プロパティをシグナルに変換します。
Zone.js からの移行パターン
既存のアプリケーションを Zone.js からゾーンレスに移行するプロセスは、次のことが可能です。 ある方法で対処された 増分。全てを書き直す必要はありません at Once: Angular は、シグナル コードと レガシーコードが共存します。
5 段階の移行戦略
増分移行計画
-
フェーズ 1 - 分析: プロパティを使用するすべてのコンポーネントを識別します
リアクティブ状態の単純 (非シグナル)。次のようなパターンを探します
{{ property }}テンプレート内 -
ステップ 2 - ローカル状態を変換します。 変換コンポーネントのプロパティ
シグナルで。追加してテンプレートを更新します
()読書に -
ステップ 3 - サービスを変換します。 移行 i
BehaviorSubjectSignals のサービス プロパティとasReadonly() -
ステップ 4 - 入力/出力を変換します。 交換する
@Input()coninput()e@Output()conoutput() -
ステップ 5 - Zone.js を削除します。 すべてのコンポーネントがシグナルを使用すると、
アクティブな
provideZonelessChangeDetection()そしてZone.jsを削除します
自動移行の概略図
Angular CLI は、移行の一部を自動化する回路図を提供します。これらのツール コードを分析し、必要な変換を適用します。
# Migra @Input() a input()
ng generate @angular/core:signal-input-migration
# Migra @Output() a output()
ng generate @angular/core:output-migration
# Migra @ViewChild/@ViewChildren a viewChild()/viewChildren()
ng generate @angular/core:signal-queries-migration
# Verifica la compatibilità zoneless
# (controlla se ci sono pattern incompatibili)
ng generate @angular/core:zoneless-migration
移行中の共存パターン
移行中は、移行されたコンポーネントと移行されていないコンポーネントの両方が存在する可能性があります。 アプリケーション。ここでは、同棲をうまくやっていく方法をご紹介します。
@Component({
selector: 'app-hybrid',
standalone: true,
template: `
<!-- Signal: funziona perfettamente in zoneless -->
<p>Signal: {{ countSignal() }}</p>
<!-- Observable con async pipe: funziona in zoneless -->
<p>Observable: {{ data$ | async }}</p>
<!-- Proprietà semplice: NON si aggiorna automaticamente -->
<!-- Richiede markForCheck() per aggiornamenti asincroni -->
<p>Legacy: {{ legacyCount }}</p>
`
})
export class HybridComponent {
// Già migrato
countSignal = signal(0);
// Funziona grazie a async pipe
data$ = inject(HttpClient).get('/api/data');
// Da migrare: servira markForCheck() in zoneless
legacyCount = 0;
private cdr = inject(ChangeDetectorRef);
updateLegacy() {
setTimeout(() => {
this.legacyCount++;
this.cdr.markForCheck(); // Necessario senza Zone.js
}, 1000);
}
}
移行前チェックリスト
- すべてのコンポーネントを確認してください
ChangeDetectionStrategy.DefaultOnPush で動作します (およびゾーンレスでのデフォルトの動作) - のすべての使用法を検索
NgZone.runOutsideAngular()そしてそれらがまだ必要かどうかを評価する - Zone.js に依存するサードパーティ ライブラリを特定する
- 単体テストが依存していないことを確認してください
zone.js/testing非同期操作の場合 - ルーターのリゾルバーとガードが Signals または Observable (単純なプロパティではない) を使用していることを確認します。
パフォーマンスのベンチマーク
Zone.js を排除すると、目に見える大きなメリットがもたらされます。データはこちら さまざまなサイズの Angular アプリケーションの実際のベンチマークから収集されました。
ベンチマーク: 電子商取引アプリケーション (50 以上のコンポーネント)
| メトリック | Zone.js を使用する | ゾーンレス | 改善 |
|---|---|---|---|
| バンドルのサイズ (gzip) | 285KB | 272KB | -13KB (-4.5%) |
| バンドルサイズ (非圧縮) | 1.2MB | 1.1MB | -100KB+ (-8%) |
| インタラクティブ化までの時間 (TTI) | 2.8秒 | 2.3秒 | -500ms (-18%) |
| 初めてのコンテンツフルペイント | 1.4秒 | 1.2秒 | -200ms (-14%) |
| 相互作用による CD サイクル | 12-15 | 1-3 | -80% サイクル |
| 中程度のヒープメモリ | 45MB | 38MB | -7 MB (-15%) |
| ブートストラップ時間 | 320ミリ秒 | 260ミリ秒 | -60ms (-19%) |
最も顕著な改善は、多くのコンポーネントを含むアプリケーションで観察されます。 そして頻繁な非同期操作。何百ものコンポーネントとアップデートを含むアプリ内 リアルタイム (WebSocket を使用したダッシュボードなど)、変更検出サイクルの短縮 を克服できる 90%.
違いに気づく場所
- 大規模なアプリケーション: コンポーネントが増えれば増えるほど、ツリー全体をチェックしなくても済むメリットが大きくなります。
- リアルタイム更新: ダッシュボード、チャット、通知: 各更新は、影響を受けるコンポーネントにのみ影響します。
- アニメーション: を基にしたアニメーション
requestAnimationFrameグローバルな変更検出をトリガーしなくなりました - モバイルデバイス: バンドルと CPU オーバーヘッドの削減により、リソースが限られたデバイスでのエクスペリエンスが大幅に向上します
- 起動時間: ブートストラップ中の Zone.js モンキーパッチ適用を排除することで、初期読み込みが高速化されます
サードパーティのコンポーネントとライブラリ
ゾーンレス移行における主な懸念事項の 1 つは互換性です。 サードパーティのライブラリを使用します。良いニュースは、ほとんどの図書館が 成熟した Angular はすでに互換性があるか、適応中です。
主なライブラリの互換性状況
| 本棚 | ゾーンレスの互換性 | 注意事項 |
|---|---|---|
| 角のあるマテリアル | 完全版 (v18+) | Signals によるネイティブ サポート |
| 角度 CDK | 完全版 (v18+) | オーバーレイ、ドラッグ&ドロップ、仮想スクロール対応 |
| PrimeNG | 完全版 (v18+) | ゾーンレス用に更新されたコンポーネント |
| NGRx | 完全版 (v18+) | ネイティブ SignalStore、selectSignal() を使用したストア |
| NGXS | 部分的 | 非同期パイプまたはmarkForCheckが必要です |
| ng-ブートストラップ | 部分的 | 一部のコンポーネントには markForCheck が必要です |
| 移動 | 完了 | 互換性のあるパイプとディレクティブ |
| 角火 | 完了 | Observable ベース、非同期パイプまたは toSignal で動作します |
互換性のないライブラリの管理
サードパーティのライブラリがまだゾーンレス モデルと互換性がない場合は、そこにあります。 状況を管理するためのさまざまな戦略。
// Strategia 1: Wrapper Component con markForCheck
@Component({
selector: 'app-legacy-wrapper',
standalone: true,
template: `
<legacy-datepicker
[value]="selectedDate()"
(dateChange)="onDateChange($event)">
</legacy-datepicker>
<p>Data selezionata: {{ selectedDate() }}</p>
`
})
export class LegacyWrapperComponent {
selectedDate = signal('');
private cdr = inject(ChangeDetectorRef);
onDateChange(date: string) {
this.selectedDate.set(date);
// Forza l'aggiornamento se la libreria non
// trigghera il change detection automaticamente
this.cdr.markForCheck();
}
}
// Strategia 2: Usare afterNextRender per operazioni DOM
@Component({
selector: 'app-chart-wrapper',
standalone: true,
template: `<div #chartContainer></div>`
})
export class ChartWrapperComponent {
chartContainer = viewChild.required<ElementRef>('chartContainer');
data = input.required<number[]>();
constructor() {
// afterNextRender e zoneless-compatible
afterNextRender(() => {
this.initChart();
});
// Reagisci ai cambiamenti dei dati
effect(() => {
const newData = this.data();
const container = this.chartContainer();
if (container) {
this.updateChart(container.nativeElement, newData);
}
});
}
private initChart() { /* ... */ }
private updateChart(el: HTMLElement, data: number[]) { /* ... */ }
}
ゾーンレス モデルのベスト プラクティス
Adopting the zoneless model doesn't just remove Zone.js: it requires a change in the way of thinking about the reactivity of the components.統合されたベストプラクティスは次のとおりです コミュニティと Angular チームから。
Angular ゾーンレスの 10 のベスト プラクティス
-
すべての反応状態はシグナルである必要があります。 物件の場合
テンプレートに表示される場合は、
signal()またはcomputed()。 単純なプロパティでは更新がトリガーされない -
アメリカ合衆国
computed()派生状態の場合: 重複しないでください 計算可能な状態。値が他のシグナルに依存する場合、それは計算された値です。 -
あなたが好む
input()eoutput(): デコレーターたち レガシー@Input()e@Output()それらは機能しますが、機能しません 信号モデルに最適化 -
避ける
NgZone: 使用する場合NgZone.runOutsideAngular()最適化のために、ゾーンレスでは必要なくなりました。これらのパターンを削除します -
アメリカ合衆国
effect()副作用について: ローカルストレージ、分析、 ロギング: 派生していないものはすべて有効になります。 -
ヘッドなし
fakeAsync: ゾーンレスモードでは、fakeAsyncetick()彼らは機能しません。アメリカ合衆国async/awaitefixture.whenStable() -
サービス内の状態をカプセル化します。 パターンを使用する
プライベート
WritableSignal+公共asReadonly()防ぐために 制御されない変化 -
悪用する
toSignal()オブザーバブルの場合: 変換する Signals で監視可能な HTTP とサービスをテンプレートで直接使用する -
アメリカ合衆国
afterNextRenderDOM の場合: の代わりにngAfterViewInitタイムアウトあり、使用afterNextRender()それは ネイティブおよびゾーンレス互換 - パフォーマンスを監視します。 Angular DevTools を使用してその数を確認してください コンポーネントは各サイクルでチェックされます。ゾーンレスの場合、数値は次のようになります。 最低限の
ゾーンレスモードでのテスト
Zone.js を使用せずに動作する場合、テストにはいくつかの変更が必要です。変化 主な問題は、Zone.js ベースのテスト ユーティリティの廃止です。
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
describe('CounterComponent (Zoneless)', () => {
let component: CounterComponent;
let fixture: ComponentFixture<CounterComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CounterComponent],
providers: [
// Usa zoneless anche nei test
provideZonelessChangeDetection()
]
}).compileComponents();
fixture = TestBed.createComponent(CounterComponent);
component = fixture.componentInstance;
});
it('should increment the counter', async () => {
// Verifica stato iniziale
expect(component.count()).toBe(0);
// Esegui azione
component.increment();
// Attendi che Angular processi i cambiamenti
await fixture.whenStable();
// Verifica il DOM aggiornato
const el = fixture.nativeElement.querySelector('p');
expect(el.textContent).toContain('1');
});
it('should update derived state', async () => {
component.count.set(5);
// In zoneless, fixture.detectChanges() funziona ancora
fixture.detectChanges();
expect(component.doubleCount()).toBe(10);
});
// NOTA: fakeAsync e tick() NON funzionano in zoneless
// Usa async/await al loro posto
it('should handle async operations', async () => {
component.loadData();
// Attendi il completamento delle operazioni async
await fixture.whenStable();
fixture.detectChanges();
expect(component.data().length).toBeGreaterThan(0);
});
});
ゾーンレスにしてはいけない場合
Zone.js が引き続き必要になる可能性があるシナリオ
- 非常に大規模なレガシー コードベース: 単純なプロパティを持つコンポーネントが何百もあり、それらすべてを移行できない場合は、Zone.js を維持することが現実的な選択となる可能性があります。
- 互換性のないライブラリに対する重大な依存関係: アプリがゾーンレスをサポートしておらず、代替手段がないライブラリに大きく依存している場合
- チームの準備ができていません: 移行には、すべての開発者が Signal モデルを理解する必要があります。チームがトレーニングされていない場合、移行によってバグが発生する可能性があります
- 厳格な期限: 移行には徹底的なテストに時間がかかります。重要なリリースの前に強制しないでください
これらすべてのケースにおいて、最善の戦略は、段階的な移行を計画することです。 新しいコンポーネントをシグナルに変換することから始めて、既存のコンポーネントを段階的に移行します。
概要と次のステップ
ゾーンレス モデルは、Angular の将来の方向性を表します。 Zone.js を廃止することで、 アプリケーションはより軽く、より速く、より理解しやすくなります。の 変更検出はもはや神秘的でグローバルなプロセスではなく、正確なメカニズムです シグナルに基づいて追跡可能です。
この記事の重要な概念
- ゾーン.js モンキーパッチを使用してすべての非同期 API をインターセプトし、オーバーヘッドと過剰な変更の検出を引き起こしました
- ゾーンレス プッシュベースのきめ細かい変更検出にシグナルを使用する
- 構成:
provideZonelessChangeDetection()+ ポリフィルからzone.jsを削除 - 非同期コード: Signals および Angular アップデートを介してステータスを自動的に更新します
- 移行: 5 ステップのインクリメンタル、自動回路図でサポート
- パフォーマンス: バンドルの削減 (-13KB gzip)、TTI の向上 (-18%)、CD サイクルの減少 (-80%)
- 本棚: Angular マテリアル、PrimeNG、NgRx はすでに互換性があります。その他の場合は、wrapper を使用します
markForCheck() - テスト: アメリカ合衆国
async/awaitefixture.whenStable()の代わりにfakeAsync/tick
シリーズの次の記事では、 @if、@for、@defer を使用した新しいテンプレート、
構造ディレクティブに代わるAngularの新しい制御フロー *ngIf,
*ngFor e *ngSwitch より読みやすく、パフォーマンスの高い構文を使用
そしてタイプセーフです。







