iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 28
1
Modern Web

Angular 大師之路系列 第 28

[Angular 大師之路] Day 28 - 認識 AsyncPipe (2) - 進階技巧

今天我們來認識一下兩個重要的 AsyncPipe 特性,可以幫助我們在使用 AsyncPipe 時更有信心,打造出更高效能的程式!

類型:觀念/技巧

難度:5 顆星

實用度:4 顆星

特性 1:自動退訂

先來看看這段簡單的程式碼:

import { Component, OnInit, OnDestroy} from '@angular/core';
import { interval } from 'rxjs';

@Component({
  selector: 'app-counter',
  template: `{{ value }}`
})
export class CounterComponent implements OnInit, OnDestroy {
  value = 0

  ngOnInit() {
    interval(1000).subscribe((counter) => {
      console.log(counter);
      this.value = counter;
    })
  }

  ngOnDestroy() {
    console.log('destroy');
  }
}


@Component({
  selector: 'my-app',
  template: `
    <app-counter *ngIf="display"></app-counter>
    <button (click)="display = !display">Toggle</button>
  `,
})
export class AppComponent  {
  display = true;
}

在上面的程式碼中,我們設計了 CounterComponent 並使用 RxJS 的 interval() 在訂閱後每秒變更一次資料,另外在畫面上設計一個按鈕來決定是否需要銷毀這個元件,當 displayfalse 時,<app-counter> 元件將會被銷毀,而當 displaytrue 時, <app-counter> 元件將重新產生。

看起來一切沒什麼問題,但是當我們打開 F12 時會發現,雖然元件被摧毀了,但 interval() 的行為並沒有停止!這將會造成每次產生元件時,就會產生一段新的 interval() ,當次數多了後將會佔據大量的記憶體,進而發生 memory leak 的問題;要避免這問題,最直覺的方式是當元件要被璀毀時,於 ngOnDestroy 方法內使用 unsubscribe 取消訂閱:

export class CounterComponent implements OnInit, OnDestroy {
  value = 0
  subscription: Subscription;

  ngOnInit() {
    this.subscription = interval(1000).subscribe((counter) => {
      console.log(counter);
      this.value = counter;
    })
  }

  ngOnDestroy() {
    console.log('destroy');
    this.subscription.unsubscribe();
  }
}

雖然取消訂閱人人有責,但是當程式中的 observable 越來越多時,總是會有不小心忘記訂閱的時候,這時 AsyncPipe 就能派上大作用啦!

使用 AsyncPipe 的自動退訂機制

AsyncPipe 的程式碼可以看到,當 AsyncPipe 處理 observable 時,會在 ngOnDestoy 時自動將 observable 退訂!因此上面的程式我們可以簡單改寫為:

@Component({
  selector: 'app-counter',
  template: `{{ value$ | async }}`
})
export class CounterComponent implements OnInit {
  value$: Observable<number>;

  ngOnInit() {
    this.value$ = interval(1000).pipe(
      tap(counter => console.log(counter))
    );
  }
}

由於 AsyncPipe 會自動退訂的關係,我們不再需要手動執行退訂的程式,整個程式看起來是不是清爽多啦!

特性 2:自動要求變更偵測

AsyncPipe 程式碼的另一角落,我們可以發現在資料變更時(不管是 Promise 還是 RxJS),會自動使用 ChangeDetectorRefmarkForCheck() 方法,自動要求變更偵測發生;會有這樣的程式需求也不難理解,當我們給予一個 observable 實體時,不管內部的值再怎麼變化,observable 的實體參考位置也不會變化,因此當元件的變更偵測策略為 OnPush 時,使用 AsyncPipe 就會發生沒有進行變更偵測的問題!所以 AsyncPipe 在訂閱(或呼叫 then())的同時,也會要求變更偵測需要處理!

搭配 OnPush 策略

透過上述提到的特性,如果元件中只剩下 observable + AsyncPipe 時,我們就可以光明正大地把元件的 OnPush 策略打開,並且完全不用手動去呼叫 markForCheck 方法,AsyncPipe 會在需要變更偵測時主動幫我們處理!

@Component({
  selector: 'app-counter',
  template: `{{ (data$ | async)?.value }}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent implements OnInit {
  @Input() data$: Observable<any>;
  ngOnInit() { }
}

從此以後每個單純用來顯示資料的元件,再打開 OnPush 之後,既能夠維持元件一定程度的高效能,又不怕忘記呼叫 markForCheck 啦!

善用 RxJS 與 AsyncPipe,要打造出既好維護,相對效能又高的元件一點都不困難啊!!

今天的程式碼參考連結:

https://stackblitz.com/edit/ironman2019-asyncpipe-onpush?file=src/app/app.component.ts


上一篇
[Angular 大師之路] Day 27 - 認識 AsyncPipe (1) - 基本使用技巧
下一篇
[Angular 大師之路] Day 29 - 在 Angular 中應用 RxJS 的 operators (1) - 基礎篇
系列文
Angular 大師之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Ho.Chun
iT邦新手 5 級 ‧ 2018-12-11 12:09:13

不好意思,請問一下,所以
如果在 component 裡面有寫到 subscribe() 的話,那麼在 ngDestory 時,一定要去做 unsubscribe()
這樣理解是對的嗎 ??

不完全正確,Angular 內建的服務如果有 observable 的話,多半會再元件 destroy 時幫你做 ubsubscribe

另外如果在元件 destroy 前就 complete 的話,也不會有 unsubscribe 的必要(如 HttpClient)

簡單來說,當你知道某個 observable 在 destroy 後不會結束的話,就需要手動處理 unsubscribe

如果真的無法判斷,全部手動 unsubscribe 也是無所謂的

Ho.Chun iT邦新手 5 級 ‧ 2018-12-11 14:19:57 檢舉

原來如此! 我懂了^^ 感謝感恩/images/emoticon/emoticon41.gif

我要留言

立即登入留言