영역 없는 변경 감지: 안녕 Zone.js
Angular 18을 통해 팀은 애플리케이션을 실행하는 실험적 기능을 도입했습니다. Zone.js 없이, Angular 21에서는 이 기능이 안정화되었으며 생산 준비가 완료되었습니다. Zone.js의 제거는 변경 사항 중 하나를 나타냅니다. 프레임워크 역사상 가장 중요한 아키텍처이며 다음에 직접적인 영향을 미칩니다. 성능, 번들 크기 e 정신 모델의 단순성.
이 시리즈의 두 번째 기사에서는 현대적인 각도 우리는 자세히 볼 것이다 Zone.js가 무엇인지, 왜 거의 10년 동안 프레임워크의 중심이 되었는지, 그리고 어떻게 새로운 zoneless 모델은 다음을 기반으로 합니다. 신호 마침내 그것을 불필요하게 만듭니다. 구성부터 마이그레이션까지 실제 벤치마크와 실무 패턴을 통해 이 가이드는 도약하는 데 필요한 모든 것을 다룹니다.
최신 Angular 시리즈 개요
| # | Articolo | 집중하다 |
|---|---|---|
| 1 | 각도 신호 | 세분화된 응답성 |
| 2 | 현재 위치 - 영역 없는 변경 감지 | Zone.js 삭제 |
| 3 | 새로운 템플릿 @if, @for, @defer | 현대적인 제어 흐름 |
| 4 | 독립형 구성 요소 | NgModule이 없는 아키텍처 |
| 5 | 신호 형태 | 신호가 포함된 반응형 양식 |
| 6 | SSR 및 증분 수화 | 서버 측 렌더링 |
| 7 | Angular의 핵심 웹 바이탈 | 성능 및 지표 |
| 8 | 각도 PWA | 프로그레시브 웹 앱 |
| 9 | 고급 종속성 주입 | DI 트리 셰이크 가능 |
| 10 | Angular 17에서 21로 마이그레이션 | 마이그레이션 가이드 |
Zone.js는 무엇이며 왜 문제가 됩니까?
Zone.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를 패치합니다.
영향을 시각화하기 위해 하나의 구성 요소가 있는 애플리케이션을 예로 들어 보겠습니다.
매초마다 업데이트되는 시계를 보여줍니다. Zone.js를 사용하면 setInterval
변경 감지 주기를 트리거합니다.전체 구성요소 트리
템플릿의 단일 바인딩만 업데이트해야 하는 경우에도 매 틱마다.
기존 변경 감지의 작동 방식
영역 없는 모델을 이해하려면 변경 감지가 작동하는 방식을 이해하는 것이 중요합니다. 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와 Zoneless 흐름 비교
| 단계 | Zone.js 사용 | 존리스(신호) |
|---|---|---|
| 이벤트 차단 | Zone.js가 API를 패치합니다. | 차단 없음 |
| 프레임워크에 알림 | NgZone이 이벤트를 발생시킵니다. | The Signal은 소비자에게 알립니다. |
| 수표 범위 | 전체 구성요소 트리 | 신호를 읽는 구성요소만 |
| 전략 | 더티 검사(값 비교) | 푸시 기반(직접 알림) |
| 제어되는 구성요소 | 모두(또는 OnPush까지 모두) | "더러운"이라고 표시된 것만 |
| 쓸모없는 트리거 | 자주(모든 비동기 작업) | 0(실제 변경 시에만) |
근본적인 문제는 분명합니다. Zone.js를 사용하면 Angular는 이를 모릅니다. 무엇 변경되었습니다. 그 사람은 그걸 알고 있을 뿐이야 뭔가 그럴지도 변경됩니다. 그래서 그는 모든 것을 확인해야합니다. 신호를 통해 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() - ChangeDetector참조: 다음을 사용하여 수동으로 강제로 검사할 수 있습니다.
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단계: Polyfill에서 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() }},
자동으로 하나를 녹음합니다 의존 해당 바인딩과 신호 사이.
신호가 변경되면 프레임워크는 어떤 뷰를 업데이트해야 하는지 정확히 알고 있습니다.
@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에 알림이 전달됩니다. Signals를 사용하면 이런 일이 발생합니다. 자동으로. 하지만 레거시 코드나 특별한 경우에는 개입이 필요할 수도 있습니다. 수동으로.
사례 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에서 zoneless로 기존 애플리케이션을 마이그레이션하는 것은 다음과 같은 프로세스입니다. 방식으로 처리됨 증분. 모든 것을 다시 작성할 필요는 없습니다. 즉시: Angular는 신호 코드와 레거시 코드가 공존합니다.
5단계 마이그레이션 전략
증분 마이그레이션 계획
-
1단계 - 분석: 속성을 사용하는 모든 구성 요소를 식별합니다.
반응 상태의 경우 단순(비신호)입니다. 다음과 같은 패턴을 찾아보세요.
{{ property }}템플릿에서 -
2단계 - 로컬 상태 변환: 구성요소 속성 변환
신호에서. 추가하여 템플릿 업데이트
()독서에 -
3단계 - 서비스 변환: 마이그레이션
BehaviorSubject및 신호의 서비스 속성asReadonly() -
4단계 - 입력/출력 변환: 바꾸다
@Input()~와 함께input()e@Output()~와 함께output() -
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비동기 작업의 경우 - 라우터 해석기 및 가드가 신호 또는 관찰 가능(단순 속성이 아님)을 사용하는지 확인하십시오.
성능 벤치마크
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 | -7MB(-15%) |
| 부트스트래핑 시간 | 320ms | 260ms | -60ms(-19%) |
가장 중요한 개선 사항은 구성 요소가 많은 응용 프로그램에서 관찰됩니다. 빈번한 비동기 작업. 수백 개의 구성 요소와 업데이트가 포함된 앱 실시간(예: WebSocket이 포함된 대시보드), 변경 감지 주기 단축 극복할 수 있다 90%.
차이점을 발견한 곳
- 대규모 애플리케이션: 구성 요소가 많을수록 전체 트리를 확인하지 않는 이점이 커집니다.
- 실시간 업데이트: 대시보드, 채팅, 알림: 각 업데이트는 영향을 받는 구성 요소에만 영향을 미칩니다.
- 애니메이션: 기반으로 한 애니메이션
requestAnimationFrame더 이상 전역 변경 감지를 트리거하지 않습니다. - 모바일 장치: 번들 및 CPU 오버헤드가 감소하여 리소스가 제한된 장치에서의 경험이 크게 향상됩니다.
- 시작 시간: 부트스트래핑 중 Zone.js 원숭이 패치를 제거하면 초기 로딩 속도가 빨라집니다.
타사 구성 요소 및 라이브러리
영역 없는 마이그레이션의 주요 관심사 중 하나는 호환성입니다. 타사 라이브러리와 함께. 좋은 소식은 대부분의 도서관이 성숙한 Angular는 이미 호환되거나 적응하는 과정에 있습니다.
주요 라이브러리의 호환성 상태
| 책장 | 영역 없는 호환성 | 메모 |
|---|---|---|
| 각도 재질 | 완료(v18+) | 신호를 통한 기본 지원 |
| 각도 CDK | 완료(v18+) | 오버레이, 드래그 앤 드롭, 가상 스크롤 호환 |
| 프라임NG | 완료(v18+) | 영역 없는 구성요소가 업데이트되었습니다. |
| NgRx | 완료(v18+) | 네이티브 SignalStore, selectSignal()을 사용하여 저장 |
| NGXS | 부분 | 비동기 파이프 또는 markForCheck 필요 |
| ng-부트스트랩 | 부분 | 일부 구성 요소에는 markForCheck가 필요합니다. |
| 움직이는 | 완벽한 | 호환되는 파이프 및 지시어 |
| 각도 화재 | 완벽한 | 관찰 가능 기반, 비동기 파이프 또는 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[]) { /* ... */ }
}
영역 없는 모델에 대한 모범 사례
영역 없는 모델을 채택하면 Zone.js가 제거될 뿐만 아니라 변경이 필요합니다. 구성 요소의 반응성에 대해 생각하는 방식으로. 통합된 모범 사례는 다음과 같습니다. 커뮤니티와 Angular 팀에서.
Angular Zoneless를 위한 10가지 모범 사례
-
모든 반응 상태는 신호여야 합니다. 재산인 경우
템플릿에 나타나면
signal()또는computed(). 단순 속성은 업데이트를 트리거하지 않습니다. -
미국
computed()파생된 상태의 경우: 중복하지 마세요 계산 가능한 상태 값이 다른 신호에 따라 달라지는 경우 이는 계산된 값입니다. -
당신은 선호
input()eoutput(): 데코레이터 유산@Input()e@Output()그들은 일하지만 그렇지 않아 Signal 모델에 최적화됨 -
피하다
NgZone: 당신이 사용하는 경우NgZone.runOutsideAngular()최적화를 위해 더 이상 영역 없는 환경에서는 필요하지 않습니다. 이 패턴을 제거하세요 -
미국
effect()부작용의 경우: 로컬스토리지, 분석, 로깅: 파생되지 않은 모든 것은 영향을 받습니다. -
없이 머리
fakeAsync: 존리스 모드에서는fakeAsyncetick()그들은 작동하지 않습니다. 미국async/awaitefixture.whenStable() -
서비스의 상태를 캡슐화합니다. 패턴을 사용하세요
비공개
WritableSignal+ 공개asReadonly()방지하기 위해 통제되지 않은 변경 -
악용하다
toSignal()Observable의 경우: 변환하다 템플릿에서 직접 사용할 수 있는 신호의 관찰 가능한 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 모델을 이해해야 합니다. 팀이 교육을 받지 않은 경우 마이그레이션으로 인해 버그가 발생할 수 있습니다.
- 엄격한 마감일: 마이그레이션에는 철저한 테스트를 위한 시간이 필요합니다. 중요한 릴리스 전에 강제로 실행하지 마십시오
이러한 모든 경우에 가장 좋은 전략은 점진적인 마이그레이션을 계획하는 것입니다. 새로운 구성 요소를 신호로 변환하는 것부터 시작하고 기존 구성 요소를 점진적으로 마이그레이션하세요.
요약 및 다음 단계
Zoneless 모델은 Angular의 미래 방향을 나타냅니다. Zone.js를 제거함으로써, 애플리케이션은 더 가벼워지고, 빨라지고, 이해하기 쉬워집니다. 는 변경 감지는 더 이상 신비하고 전역적인 프로세스가 아니라 정확한 메커니즘입니다. 신호에 의해 안내되어 추적 가능합니다.
이 기사의 주요 개념
- Zone.js 원숭이 패치로 모든 비동기 API를 가로채서 오버헤드와 과도한 변경 감지를 유발했습니다.
- 존리스 푸시 기반의 세밀한 변경 감지를 위해 신호 사용
- 구성:
provideZonelessChangeDetection()+ 폴리필에서 zone.js 제거 - 비동기 코드: Signals 및 Angular 업데이트를 통해 자동으로 상태 업데이트
- 이주: 5단계 증분, 자동 회로도 지원
- 성능: 번들 감소(-13KB gzip), 더 나은 TTI(-18%), 더 적은 CD 주기(-80%)
- 책장: Angular Material, PrimeNG, NgRx는 이미 호환됩니다. 다른 경우에는 래퍼를 사용하십시오.
markForCheck() - 테스트: 미국
async/awaitefixture.whenStable()대신에fakeAsync/tick
시리즈의 다음 기사에서는 @if, @for 및 @defer를 사용한 새로운 템플릿,
구조적 지시문을 대체하는 Angular의 새로운 제어 흐름 *ngIf,
*ngFor e *ngSwitch 더 읽기 쉽고 성능이 좋은 구문을 사용합니다.
그리고 유형이 안전합니다.







