NgZone
Angular爲我們提供了NgZone服務,對於一些頻繁的操作,可以不去觸發變更檢測。工作時優化性能
變更檢測
Events
- 一些事件,例如click
、change
、input
、submit
等;XMLHttpRequests
- 網絡請求;Timers
-setTimeout()
與setInterval()
API 等;
每次變更檢測都意味着額外的計算和資源消耗,如果我們需要對應用進行性能優化,那麼首先該從這個概念下手。Angular 引入 Zone.js 以處理變更檢測,具體來說,Zone.js 通過對所有常見的異步 API 打上了“補丁” 以追蹤所有的異步操作,進而使 Angular 可以決定何時刷新 UI。
Zone.js
- Zone 是一種用於攔截和跟蹤異步工作的機制。
- 在 Angular 應用中,每個 task 都會在 “Angular” Zone 中運行,這個 Zone 被稱爲 NgZone。一個 Angular 應用中只存在一個 Angular Zone,而變更檢測只會由 運行於這個 NgZone 中的異步操作觸發。
示例
函數runOutsideAngular
用於確保代碼中在NgZone之外運行,保證Angular的變更檢測不會因爲相關代碼而觸發
setInterval
定時器便不會觸發變更檢測
export class OneComponent implements OnInit {
constructor(
private ngZone: NgZone
) {
this.ngZone.runOutsideAngular(() => {
setInterval(() => {
++this.num;
}, 1000);
});
}
num = 1;
}
我們發現因爲變更檢測沒有出發,所以視圖沒有更新
<h1>{{num}}</h1>
run
方法的目的與 runOutsideAngular
正好相反:任何寫在 run 裏的方法,都會進入 Angular Zone 的管轄範圍
this.ngZone.runOutsideAngular(() => {
const token = setInterval(() => {
this.ngZone.run(() => {
++this.num;
});
if (this.num == 10) {
clearInterval(token);
}
}, 1000);
});
num = 1;
我們發現頁面更新啦,
其實這是我在源碼單元測試看到的,疑惑查了下,感覺在寫大屏的時候可以使用的
如果我們有一個一定時間後更新一個值
this.ngZone.runOutsideAngular(() => {
const token = setTimeout(() => {
this.ngZone.run(() => {
this.num = 10;
});
clearTimeout(token);
}, 5000);
});
ApplicationRef
tick
調用此方法可顯式處理更改檢測及其副作用。
export class OneComponent implements OnInit {
constructor(
private ngZone: NgZone,
private app: ApplicationRef
) {
this.ngZone.runOutsideAngular(() => {
const token = setTimeout(() => {
this.num = 100;
//調用更新檢測
app.tick();
clearTimeout(token);
}, 3000);
});
}
}
我們發現上面的案例更加適用於NgZone
那我們可不可以從髒依賴的角度上來看
@Component({
selector: "hello",
template: `
<h1>Hello {{ name }}!</h1>
<p>{{ counter }}</p>
<button (click)="click()">click</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HelloComponent {
counter: number = 0;
@Input() name: string;
constructor(private app: ApplicationRef) {
setInterval(() => this.counter++, 1000);
}
click() {
this.app.tick();
}
}
markForCheck()、OnPush、zone.js與detectChanges()
Angular 更新 HTML 的原理
- 初始化 Component 。在最開始 bootstrapping Angular Application 的時候,Angular 會載入 bootstrap component 並調用
ApplicationRef.tick()
方法來觸發 變更策略檢測 - 監聽事件,如果DOM的事件更新了 Angular component 裏面的data, 那麼 變更策略檢測 觸發
- HTTP 數據請求。如果在 Angular component 裏更新了請求的數據
- 宏任務微任務 ,方法裏更新了 Angular component 的數據。比如
setTimeout()
,setInterval()
。Promise.then()
裏更新了 Angular component 的數據
zone.js
Angular 自帶了 ngZone
的 service,這個 service 會創建一個叫 ‘angular’ 的 Zone。在這個 Angular Zone 裏,當沒有 schedule MicroTasks 或是當執行 sync/async function 的時候會自動觸發 change detection。如果 Angular 的檢測策略是的 default
的,那麼所有的 asynchronous operations 都是在 Angular Zone 裏,並且都會按以上條件自動觸發 change detection。
不觸發
this.ngZone.runOutsideAngular(() => {
不觸發
});
this.ngZone.run(() => {
觸發
});
detectChanges()
當這個第三方 APIs 的方法 update data 的時候,這個 update 是在 Angular Zone 之外的,所以 Angular 並不會知道這個 update 的發生,因此也不會觸發變更策略檢測。在這個情況下,就可以調用 ChangeDetectorRef.detectChanges()
來手動觸發變更策略檢測 也可以放在zone.run()
方法
關於 OnPush
和 markForCheck()
如果 Angular 的檢測策略被設置爲 OnPush
,那麼只有以下條件下 change detection 纔會被觸發:
- 當帶有
@Input
property 的引用完全變化或是完全被新的值替換的時候。 - 噹噹前 component 或是 child component 觸發 event 的時候,例如 Click,KeyUp,Subscription 等等。
很多時候 update 就不會觸發變更策略檢測。這時就必須通過調用 markForCheck()
來手動告訴 Angular 去檢查並更新 view。所以總體來說,markForCheck()
基本上只在 Angular 的檢測策略被設置爲 OnPush
的時候會被需要使用到。
markForCheck()
和 detectChanges()
的核心區別
最後,總結一下 markForCheck()
和 detectChanges()
的核心區別。detectChanges()
會真正觸發 Angular 的 change detection,而 markForCheck()
則不會。
當我們禁用NoopZone
Zone
幫助Angular知道何時觸發更改檢測,並使開發人員專注於應用程序開發。默認情況下,Zone
已加載且無需額外配置即可工作。但是,您不必一定要使用Zone
Angular。相反,您可以選擇自己觸發更改檢測。
要刪除Zone.js,請進行以下更改。
-
zone.js
從中刪除導入polyfills.ts
:// import 'zone.js/dist/zone'; // Included with Angular CLI.`
-
Bootstrap Angular的
noop
區域位於src/main.ts
:platformBrowserDynamic().bootstrapModule(AppModule, { ngZone: 'noop' }) .catch(err => console.error(err));
思考
刪除Zone.js 讓我們獲得更多性能優勢,如果一切都是ChangeDetectionStrategy.OnPush
則永遠不會觸發更改檢測週期,除非通過下面
ChangeDetectorRef.detectChanges()
ApplicationRef.tick()
注意一個易錯點ChangeDetectorRef.markForCheck
,它僅設置組件的視圖層,而不會觸發更改檢測週期