angular11源碼探索二十四[路由檢測變更策略]

NgZone

官網文檔

https://angular.io/guide/zone

Angular爲我們提供了NgZone服務,對於一些頻繁的操作,可以不去觸發變更檢測。工作時優化性能

變更檢測

  • Events - 一些事件,例如 clickchangeinputsubmit 等;
  • 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() 方法

關於 OnPushmarkForCheck()

如果 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已加載且無需額外配置即可工作。但是,您不必一定要使用ZoneAngular。相反,您可以選擇自己觸發更改檢測。

要刪除Zone.js,請進行以下更改。

  1. zone.js從中刪除導入polyfills.ts

    // import 'zone.js/dist/zone';  // Included with Angular CLI.`
    
  2. 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,它僅設置組件的視圖層,而不會觸發更改檢測週期

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章