Angular中优雅的处理RxJs自动取消订阅的方式以免出现内存泄露以及多次调用的问题

Angular中深度集成了Rxjs,只要你使用Angular框架,你就不可避免的会接触到RxJs相关的知识。

在Android开发中,绝大多数的Android开发者都用过RxJavaRxAndroidRxKotlin相关库。因其强大的操作符 以及 方便的线程切换 给我们日常开发提供了极大的便利。

但是,可能是前端并不像强类型语言那么严格,即使代码写的有点小问题,也是能照样运行,不仔细排查也发现不了什么影响。
在最近接触的Angular项目中,发现前端的小伙伴们很少去了解RxJs的原理,导致写的项目中,出现了很多只订阅不取消代码,以及 ObservableSubject 区别是什么都分不清的情况,导致项目运行时,一个事件发射出去,结果一堆订阅接收到了,甚至多次运行后,会出现重复执行几十次上百次的情况。

之前写过一篇在Android中如何优雅的处理自定取消订阅的方法,感兴趣的可以看下: AutoDispose代替RxLifecycle优雅的解决RxJava内存泄漏问题

本篇博客我们来看一下在Angular中如何优雅的处理RxJs自动取消订阅的问题。

方式一 (不推荐)

首先是常规用法,

我们在使用 subscribe 的时候会返回一个Subscription 对象,如下
在这里插入图片描述

该对象提供一个 unsubscribe() 方法,如下

在这里插入图片描述

我们只需要调用该方法即可取消订阅,一般都是在OnDestory中取消订阅。大致代码如下

  ngOnDestroy(): void {
    this.subject.unsubscribe()
  }

有时候我们不能仅限于会用,应该要知道为什么这样写。
Subject为例,我们可以大致瞧一眼,unsubscribe() 代码都写了些什么,为什么调用这个方法后就不会收到next() 发射的数据流了。

首先看下unsubscribe()
在这里插入图片描述

再看一下 next() 方法
在这里插入图片描述

代码一目了然,并没有什么高大上的代码,其实就是unSubscribe的时候将 closedisStopped 置为true, 在 next 的时候判断一下,如果已经close或者stop的话,就不发射数据了。

好了,扯远了,回归主题,为什么不建议这么写。

因为当你一个类中的 Subscription 特别多的时候,那么这种写法就很麻烦,每个都要 unsubscribe() 一遍,显然是不合适的。

方式二

使用takeUntil 操作符代替unsubscribe
我们先来看一下 takeUntil 操作符是干嘛的,以下是复制的源码及注释。

/**
 * Emits the values emitted by the source Observable until a `notifier`
 * Observable emits a value.
 *
 * <span class="informal">Lets values pass until a second Observable,
 * `notifier`, emits a value. Then, it completes.</span>
 *
 * ![](takeUntil.png)
 *
 * `takeUntil` subscribes and begins mirroring the source Observable. It also
 * monitors a second Observable, `notifier` that you provide. If the `notifier`
 * emits a value, the output Observable stops mirroring the source Observable
 * and completes. If the `notifier` doesn't emit any value and completes
 * then `takeUntil` will pass all values.
 *
 * ## Example
 * Tick every second until the first click happens
 * ```ts
 * import { fromEvent, interval } from 'rxjs';
 * import { takeUntil } from 'rxjs/operators';
 *
 * const source = interval(1000);
 * const clicks = fromEvent(document, 'click');
 * const result = source.pipe(takeUntil(clicks));
 * result.subscribe(x => console.log(x));
 * ```
 *
 * @see {@link take}
 * @see {@link takeLast}
 * @see {@link takeWhile}
 * @see {@link skip}
 *
 * @param {Observable} notifier The Observable whose first emitted value will
 * cause the output Observable of `takeUntil` to stop emitting values from the
 * source Observable.
 * @return {Observable<T>} An Observable that emits the values from the source
 * Observable until such time as `notifier` emits its first value.
 * @method takeUntil
 * @owner Observable
 */
export function takeUntil<T>(notifier: Observable<any>): MonoTypeOperatorFunction<T> {
  return (source: Observable<T>) => source.lift(new TakeUntilOperator(notifier));
}

注释的大概意思就是,当takeUntil里面的notifier接收到值时,就会终止数据流。这样一来,就达到了我们的目的。

大致用法如下:

 import {Component, OnDestroy, OnInit} from '@angular/core';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {


  private subject = new Subject();

  private $destory = new Subject<boolean>();

  constructor() {

  }


  /**
   * 组件初始化完毕时调用
   */
  ngOnInit(): void {

    this.subject
      .pipe(
        takeUntil(this.$destory)//需要注意takeUntil要放在最后
      )
      .subscribe(data => {
        console.log('data:', data);
      });

  }


  ngOnDestroy(): void {
    //this.subject.unsubscribe()
    this.$destory.next(true);
    this.$destory.unsubscribe();
  }


}

这样一来即使一个组件中有很多订阅,也不用在 ngOnDestroy() 中写很多unSubscribe。

但是这样还是有些麻烦,毕竟每个组件都要写

 private $destory = new Subject<boolean>();

  ngOnDestroy(): void {
    //this.subject.unsubscribe()
    this.$destory.next(true);
    this.$destory.unsubscribe();
  }

这种重复代码。下面我们基于方式二稍微优化一下。

方式二优化(推荐)

我们可以写一个BaseComponent,将一些使用率很高的模板代码放到BaseComponent中,其他组件继承该组件即可。

例如:

BaseComponent

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

@Component({
  selector: 'app-base',
  templateUrl: './base.component.html',
  styleUrls: ['./base.component.css']
})
export class BaseComponent implements OnInit, OnDestroy {

  $destory: Subject<boolean> = new Subject<boolean>();

  constructor() {
  }

  ngOnInit(): void {
  }

  ngOnDestroy(): void {
    this.$destory.next(true);
    this.$destory.unsubscribe();
  }

}

让我们的组件继承BaseComponent,然后删除无用的模板代码

import {Component} from '@angular/core';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {BaseComponent} from './base/base.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent extends BaseComponent {

  private subject = new Subject();

  constructor() {
    super();//继承后要加上super()
  }


  /**
   * 组件初始化完毕时调用
   */
  ngOnInit(): void {

    this.subject
      .pipe(
        takeUntil(this.$destory)//需要注意takeUntil要放在最后
      )
      .subscribe(data => {
        console.log('data:', data);
      });

  }


}

其他组件同样继承BaseComponent即可。

我们还可以添加很多模板代码放到BaseComponent中,这样一来就可以减少很多无用的代码了,而且维护起来更加容易。


如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,码字不易,转载请注明转自喻志强的博客 ,谢谢!

©️2020 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值