Zone.js 是一種信令機制,Angular 使用它來檢測應用狀態可能何時已更改。它會捕獲非同步操作,如 setTimeout、網路請求和事件監聽器。Angular 基於來自 Zone.js 的訊號來排程變更檢測。
在某些情況下,排程的 任務 或 微任務 不會對資料模型進行任何更改,這使得執行變更檢測變得不必要。常見的例子有:
requestAnimationFrame、setTimeout 或 setInterval本節介紹如何識別此類別情況,以及如何在 Angular Zone 之外執行程式碼以避免不必要的變更檢測呼叫。
你可以使用 Angular DevTools 檢測不必要的變更檢測呼叫。它們通常在效能分析器的時序圖中顯示為連續的條形,其來源為 setTimeout、setInterval、requestAnimationFrame 或事件處理程式。當你的應用中對這些 API 的呼叫有限時,變更檢測呼叫通常是由第三方函式庫引起的。
在上圖中,有一系列由與元素關聯的事件處理程式觸發的變更檢測呼叫。這是使用第三方非原生 Angular 元件時的一個常見挑戰,這些元件不會改變 NgZone 的預設行為。
NgZone 外部執行任務
在這種情況下,你可以指示 Angular 避免為使用 NgZone 的給定程式碼片段排程的任務呼叫變更檢測。
1import { Component, NgZone, OnInit } from '@angular/core';23@Component(...)4class AppComponent implements OnInit {5 private ngZone = inject(NgZone);67 ngOnInit() {8 this.ngZone.runOutsideAngular(() => setInterval(pollForUpdates), 500);9 }10}
上面的程式碼片段指示 Angular 在 Angular Zone 之外呼叫 setInterval,並在 pollForUpdates 執行後跳過執行變更檢測。
當第三方函式庫的 API 在 Angular Zone 內被呼叫時,它們通常會觸發不必要的變更檢測週期。這種現象尤其會影響那些設定事件監聽器或啟動其他任務(如定時器、XHR 請求等)的函式庫。透過在 Angular Zone 之外呼叫函式庫 API 來避免這些額外的週期:
1import { Component, NgZone, OnInit } from '@angular/core';2import * as Plotly from 'plotly.js-dist-min';34@Component(...)5class AppComponent implements OnInit {6 private ngZone = inject(NgZone);78 ngOnInit() {9 this.ngZone.runOutsideAngular(() => {10 Plotly.newPlot('chart', data);11 });12 }13}
在 runOutsideAngular 中執行 Plotly.newPlot('chart', data); 指示框架在執行由初始化邏輯排程的任務後不應執行變更檢測。
例如,如果 Plotly.newPlot('chart', data) 向 DOM 元素新增事件監聽器,則 Angular 在執行其處理程式後不會執行變更檢測。
但有時,你可能需要監聽由第三方 API 排程的事件。在這種情況下,重要的是要記住,如果初始化邏輯在那裡完成,這些事件監聽器也將在 Angular Zone 之外執行:
1import { Component, NgZone, OnInit, output } from '@angular/core';2import * as Plotly from 'plotly.js-dist-min';34@Component(...)5class AppComponent implements OnInit {6 private ngZone = inject(NgZone);78 plotlyClick = output<Plotly.PlotMouseEvent>();910 ngOnInit() {11 this.ngZone.runOutsideAngular(() => {12 this.createPlotly();13 });14 }1516 private async createPlotly() {17 const plotly = await Plotly.newPlot('chart', data);1819 plotly.on('plotly_click', (event: Plotly.PlotMouseEvent) => {20 // This handler will be called outside of the Angular zone because21 // the initialization logic is also called outside of the zone. To check22 // whether we're in the Angular zone, we can call the following:23 console.log(NgZone.isInAngularZone());24 this.plotlyClick.emit(event);25 });26 }27}
如果你需要向父元件排程事件並執行特定的檢視更新邏輯,你應該考慮重新進入 Angular Zone 以指示框架執行變更檢測或手動執行變更檢測:
1import { Component, NgZone, OnInit, output } from '@angular/core';2import * as Plotly from 'plotly.js-dist-min';34@Component(...)5class AppComponent implements OnInit {6 private ngZone = inject(NgZone);78 plotlyClick = output<Plotly.PlotMouseEvent>();910 ngOnInit() {11 this.ngZone.runOutsideAngular(() => {12 this.createPlotly();13 });14 }1516 private async createPlotly() {17 const plotly = await Plotly.newPlot('chart', data);1819 plotly.on('plotly_click', (event: Plotly.PlotMouseEvent) => {20 this.ngZone.run(() => {21 this.plotlyClick.emit(event);22 });23 });24 }25}
也可能出現將事件分派到 Angular 區域之外的情況。重要的是要記住,觸發變更檢測(例如,手動觸發)可能會導致在 Angular 區域之外建立/更新檢視。