Angular 中什麼時候取消訂閱

你可能知道當你訂閱 Observable 對象或設置事件監聽時,在某個時間點,你須要執行取消訂閱操做,進而釋放操做系統的內存。不然,你的應用程序可能會出現內存泄露。html

接下來讓咱們看一下,須要在 ngOnDestroy 生命週期鉤子中,手動執行取消訂閱操做的一些常見場景。git

手動釋放資源場景

表單

export class TestComponent {

  ngOnInit() {
    this.form = new FormGroup({...});
    // 監聽表單值的變化
    this.valueChanges  = this.form.valueChanges.subscribe(console.log);
    // 監聽表單狀態的變化                            
    this.statusChanges = this.form.statusChanges.subscribe(console.log);
  }

  ngOnDestroy() {
    this.valueChanges.unsubscribe();
    this.statusChanges.unsubscribe();
  }
}

以上方案也適用於其它的表單控件。es6

路由

export class TestComponent {
  constructor(private route: ActivatedRoute, private router: Router) { }

  ngOnInit() {
    this.route.params.subscribe(console.log);
    this.route.queryParams.subscribe(console.log);
    this.route.fragment.subscribe(console.log);
    this.route.data.subscribe(console.log);
    this.route.url.subscribe(console.log);
   
    this.router.events.subscribe(console.log);
  }

  ngOnDestroy() {
    // 手動執行取消訂閱的操做
  }
}

Renderer 服務

export class TestComponent {
  constructor(
    private renderer: Renderer2, 
    private element : ElementRef) { }

  ngOnInit() {
    this.click = this.renderer
        .listen(this.element.nativeElement, "click", handler);
  }

  ngOnDestroy() {
    this.click.unsubscribe();
  }
}

Infinite Observables

當你使用 interval()fromEvent() 操做符時,你建立的是一個無限的 Observable 對象。這樣的話,當咱們再也不須要使用它們的時候,就須要取消訂閱,手動釋放資源。github

export class TestComponent {
  constructor(private element : ElementRef) { }

  interval: Subscription;
  click: Subscription;

  ngOnInit() {
    this.interval = Observable.interval(1000).subscribe(console.log);
    this.click = Observable.fromEvent(this.element.nativeElement, 'click')
                           .subscribe(console.log);
  }

  ngOnDestroy() {
    this.interval.unsubscribe();
    this.click.unsubscribe();
  }
}

Redux Store

export class TestComponent {

  constructor(private store: Store) { }

  todos: Subscription;

  ngOnInit() {
     /**
     * select(key : string) {
     *   return this.map(state => state[key]).distinctUntilChanged();
     * }
     */
     this.todos = this.store.select('todos').subscribe(console.log);  
  }

  ngOnDestroy() {
    this.todos.unsubscribe();
  }
}

無需手動釋放資源場景

AsyncPipe

@Component({
  selector: 'test',
  template: `<todos [todos]="todos$ | async"></todos>`
})
export class TestComponent {
  constructor(private store: Store) { }
  
  ngOnInit() {
     this.todos$ = this.store.select('todos');
  }
}

當組件銷燬時,async 管道會自動執行取消訂閱操做,進而避免內存泄露的風險。typescript

Angular AsyncPipe 源碼片斷api

@Pipe({name: 'async', pure: false})
export class AsyncPipe implements OnDestroy, PipeTransform {
  // ...
  constructor(private _ref: ChangeDetectorRef) {}

  ngOnDestroy(): void {
    if (this._subscription) {
      this._dispose();
    }
  }
}

@HostListener

export class TestDirective {
  @HostListener('click')
  onClick() {
    ....
  }
}

須要注意的是,若是使用 @HostListener 裝飾器,添加事件監聽時,咱們沒法手動取消訂閱。若是須要手動移除事件監聽的話,能夠使用如下的方式:async

// subscribe
this.handler = this.renderer.listen('document', "click", event =>{...});

// unsubscribe
this.handler();

Finite Observable

當你使用 HTTP 服務或 timer Observable 對象時,你也不須要手動執行取消訂閱操做。post

export class TestComponent {
  constructor(private http: Http) { }

  ngOnInit() {
    // 表示1s後發出值,而後就結束了
    Observable.timer(1000).subscribe(console.log);
    this.http.get('http://api.com').subscribe(console.log);
  }
}

timer 操做符

  • 操做符簽名this

public static timer(initialDelay: number | Date, period: number, scheduler: Scheduler): Observable
  • 操做符做用url

timer 返回一個發出無限自增數列的 Observable,具備必定的時間間隔,這個間隔由你來選擇。

  • 操做符示例

// 每隔1秒發出自增的數字,3秒後開始發送
var numbers = Rx.Observable.timer(3000, 1000);
numbers.subscribe(x => console.log(x));

// 5秒後發出一個數字
var numbers = Rx.Observable.timer(5000);
numbers.subscribe(x => console.log(x));

最終建議

你應該儘量少的調用 unsubscribe() 方法,你能夠在 RxJS: Don’t Unsubscribe 這篇文章中瞭解與 Subject 相關更多信息。

具體示例以下:

export class TestComponent {
  constructor(private store: Store) { }

  private componetDestroyed: Subject = new Subject();
  todos: Subscription;
  posts: Subscription;

  ngOnInit() {
     this.todos = this.store.select('todos')
                      .takeUntil(this.componetDestroyed).subscribe(console.log); 
                      
     this.posts = this.store.select('posts')
                      .takeUntil(this.componetDestroyed).subscribe(console.log); 
  }

  ngOnDestroy() {
    this.componetDestroyed.next();
    this.componetDestroyed.unsubscribe();
  }
}

takeUntil 操做符

  • 操做符簽名

public takeUntil(notifier: Observable): Observable<T>
  • 操做符做用

發出源 Observable 發出的值,直到 notifier Observable 發出值。

  • 操做符示例

var interval = Rx.Observable.interval(1000);
var clicks = Rx.Observable.fromEvent(document, 'click');
var result = interval.takeUntil(clicks);

result.subscribe(x => console.log(x));

參考資源

相關文章
相關標籤/搜索