乐趣区

关于javascript:Angular4最佳实践之unsubscribe

Angular4 最佳实际之 unsubscribe

Angular4 中引入 Observable 解决各种罕用异步操作,如 EventEmitter、HttpClient、Router 以及响应式表单 Form,理论开发中也会自定义 Observable 对象,通过订阅 Observable 对象能够实现数据通信,并在组件登记时勾销订阅。
<br/>
本文将介绍 3 种勾销订阅的办法,并别离整顿几种须要手动勾销和不须要手动勾销的场景。

目录

<ul>
<li> 为什么要勾销订阅 </li>
<li> 如何勾销订阅 </li>
<li> 不须要手动勾销的场景 </li>
<li> 须要手动勾销的场景 </li>
</ul>

为什么要勾销订阅?

和 Javascript 中的 addEventLister 一样,Angular 中一旦订阅了 Observable 对象,在组件 destory 时就应该及时勾销订阅,否则会造成 内存泄露 或其余 不必要的操作
<br/>
上面这个例子通过 PageService 订阅 refresh 事件,返回一个 Observable 对象,当公布 refresh 时,申请用户信息。这里为了演示,提早了 10 秒钟,若此时通过路由跳转到其余页面,IndexComponent 将销毁,然而 10 秒后,申请仍然会收回。也就是说组件销毁了,组件内的订阅并没有主动销毁,须要手动进行勾销订阅。

export class IndexComponent implements OnInit {constructor(private page: PageService, user:UserService) {}
    ngOnInit() {this.page.refresh.delay(10000).subscribe(() => {this.user.getUser();
        });
      }
}

如何勾销订阅?

组件销毁前,须要通过 OnDestroy 生命钩子进行勾销订阅可察看对象,有 3 种办法能够实现:

1 unsubscribe()

private subscription: Subscription;
constructor(private page: PageService, user:UserService) {}
ngOnInit() {this.subscription = this.page.refresh.subscribe(() => {this.user.getUser();
    });
}
ngOnDestroy(){this.subscription.unsubscribe();
}

当组件中存在多个 subscription 时,要一个一个进行 unsubscribe 未免有些麻烦,有 2 种形式能够简化代码。

1) 通过 subscription.add()增加子 subscription

subscription.add()通过将多个 subscription 增加为子 subscription,当勾销父 subscription 时,子 subscription 也将主动勾销,该办法要求必须先执行第一个 subscription。

ngOnInit() {
    // 订阅第一个可察看对象 -parent
    this.subscription = this.page.refresh.subscribe(() => {this.user.getUser();
    });
    // 将第二个可察看对象增加为子对象 -child
    this.subscription.add(this.page.load.subscribe(() => {this.user.getData();
    }));
}
ngOnDestroy(){
      // 同时勾销 2 次订阅
      this.subscription.unsubscribe();}
2)定义 subscription 数组,组件销毁时顺次勾销订阅
private subscriptions: Subscription[]=[];
ngOnInit() {this.subscriptions.push(this.page.refresh.subscribe(() => {this.user.getUser();
    }));
    this.subscriptions.push(this.page.load.subscribe(() => {this.user.getData();
    }));
}
ngOnDestroy() {this.subscriptions.forEach(sub => sub.unsubscribe());
}

2 takeWhile()

Observable 对象的 [takeWhile] 办法将对数据流进行操作,示意:继续收回值,当传入表达式后果为 false 时,进行收回值。例子中定义了一个组件销毁标识 destroy,当 destroy==true 时,可察看对象收回的值将不再流向该组件。该办法较简洁,举荐应用

takeWhile(predicate: function(value, index): boolean): Observable
destroy: boolean = false;  // 组件销毁标识
ngOnInit() {this.page.refresh.takeWhile(() => !this.destroy).subscribe(() => {this.user.getUser();
    });
}
ngOnDestroy() {this.destroy = true;}

3 takeUntil()

Observable 对象的 [takeUntil] 办法将对数据流进行操作,示意:继续收回值,当传入 observable 收回值时,进行收回值。该办法须要传入另一个 Observable,在组件销毁时收回值,进而勾销订阅。

takeUntil(notifier: Observable): Observable
private unsubscribe = new Subject<void>();
ngOnInit() {this.page.refresh.takeUntil(this.unsubscribe).subscribe(num => {this.user.getUser();
    });
}
ngOnDestroy() {this.unsubscribe.next();  // 所有 subscription 完结
    this.unsubscribe.complete();  // 传入 observable 完结}

不须要手动勾销的场景

Angular 中有些场景已进行 unsubscribe 或通过 Observable.complete()的形式完结订阅数据流,在开发中并不需要手动再进行解决订阅。

1) Async pipe

AsyncPipe 会订阅一个可察看对象或承诺,并返回其收回的最初一个值。当收回新值时,该管道就会把这个组件标记为须要进行变更查看的。当组件销毁时,主动勾销订阅。如果只是模板中须要的变量,采纳 Async 是最佳实际

The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted, the async pipe marks the component to be checked for changes. When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks.

@Component({
    selector: 'index',
    template: `<div>refreshTime: {{refreshTime | async}}</div>`
})
export class IndexComponent implements OnInit {
    refreshTime: Observable<string>;
    constructor(private page: PageService) {}
    ngOnInit() {this.refreshTime = this.page.refresh();
    }
}
2) HostListener

通过 @HostListener 进行订阅的事件,和间接在模板里订阅事件一样,也是主动勾销的。

3) EventEmitter

EventEmitter 类派生自 Observable,Angular 提供了一个 EventEmitter 类,它用来从组件的 @Output() 属性中公布一些值。

4) 无限的 Observable

无限的 Observable 指的是收回的值是无限的,如 timer。

5) Http

http 在 load 事件实现之后进行 responseObserver.complete(),主动完结数据流,上面是 Http 源码中的局部代码。

const onLoad = (event) => {
    ...
     responseObserver.next(new Response(responseOptions));
    responseObserver.complete();};
script.addEventListener('load', onLoad);

须要手动勾销的场景

1) Router

Angular 在组件销毁时并没有勾销 router 的所有订阅事件,同样是提早 10 秒,能够看到申请仍然是会收回的。

ngOnInit() {this.subscription = this.router.queryParamMap.delay(10000).subscribe((param) => {this.user.getUser(+param.get(id));
    });
}
ngOnDestroy() {this.subscription.unsubscribe();
}
2) Forms

表单中的 valueChanges 和 statusChanges 等 Observable 都须要手动勾销。

ngOnInit() {this.form = new FormGroup({...});
    this.subscription  = this.form.valueChanges.subscribe(() => {});
}
ngOnDestroy() {this.subscription.unsubscribe();
}
3) Renderer Service
constructor(private renderer: Renderer2, private element : ElementRef) { }
    ngOnInit() {this.subscription = this.renderer.listen(this.element.nativeElement, "click", () => {});
}
ngOnDestroy() {this.subscription.unsubscribe();
}
4) fromEvent()、interval()等输入值可能为有限个的可察看对象
destroy: boolean = false;  // 组件销毁标识
ngOnInit() {Observable.interval(1000).takeWhile(() => !this.destroy).subscribe(() => {}));
    Observable.fromEvent(this.element.nativeElement, 'click').takeWhile(() => !this.destroy).subscribe(() => {}));
}
ngOnDestroy() {this.destroy = true;}
5) 自定义 Observable

所有自定义 Observable 必须在组件销毁前手动勾销。

结束语

在实践中,要时刻记得勾销可察看对象的订阅,如果只是模板中须要的变量,采纳 Async 是最佳实际,其次是通过 takeWhile()takeUntil()手动勾销订阅。

参考文献

  1. https://angular.cn/guide/obse…;br/>
  2. https://netbasal.com/when-to-…;br/>
  3. https://blog.angularindepth.c…;br/>
  4. http://brianflove.com/2016/12…

<br/>

退出移动版