禾金晶 發表於 2021-2-5 23:53:00

[译]Rxjs&Angular-退订可观察对象的n种方式

<blockquote>
<p>原文/出处: RxJS &amp; Angular — Unsubscribe Like a Pro</p>
</blockquote>
<p>在angular项目中我们不可避免的要使用<code>RxJS</code>可观察对象(<code>Observables</code>)来进行订阅(<code>Subscribe</code>)和退订(<code>Unsubscribe</code>)操作;</p>
<h2 id="概述">概述</h2>
<p>我们的每个angular项目中都会用到<code>RxJS</code>, <code>RxJS</code>在我们的angular app中对数据流和性能有非常大的影响。</p>
<p>为了避免内存泄漏,在适当的时机对可观察对象进行退订是非常重要的; 本文会向你展示各种在angular组件中退订可观察对象的方法!</p>
<p>首先,我们创建一个帮助类来帮我们创建的订阅对象(<code>Subscription</code>)</p>
<pre><code class="language-ts">
@Injectable({
providedIn: 'root',
})
export class DummyService {
getEmissions(scope: string): Observable&lt;string&gt; {
    return Observable.create((observer) =&gt; {
      console.log(`${scope} Subscribed`);

      const subscription: Subscription = timer(0, 1000)
      .pipe(
          map((n) =&gt; `${scope} Emission #${n}`),
          tap(console.log)
      )
      .subscribe(observer);

      return () =&gt; {
      subscription.unsubscribe();
      console.log(`${scope} Unsubscribed`);
      };
    });
}
}
</code></pre>
<p>我们的帮助类有一个<code>getEmissions</code>方法, 它接受一个<code>scope</code>参数来记录日志, 它的返回值是一个会每秒发出 <code>${scope} Emission #n</code>字符串的可观察对象.</p>
<h2 id="方式一-常规的取消订阅的方式">方式一 "常规"的取消订阅的方式</h2>
<p>最简单的订阅和取消订阅一个可观察对象的方式是在 <code>ngOnInit</code> 方法中订阅可观察对象(<code>Observable</code>), 然后在组件类中创建一个类属性用来保存这个订阅(<code>Subscription</code>), 并在 <code>ngOnDestroy</code> 中取消对可观察对象对订阅. 简单起见, 我们可以使用<code>Subscription.EMPTY</code>来初始化一个订阅对象(<code>Subscription</code>), 这样可以防止在取消订阅时遇到空引用对问题.</p>
<pre><code class="language-ts">@Component({
selector: 'app-regular',
template: `&lt;div&gt;{{ emission }}&lt;/div&gt;`,
})
export class RegularComponent implements OnInit, OnDestroy {
emission: string;

/*
    Note: we initialize to Subscription.EMPTY to avoid null checking within ngOnDestroy
   */
private subscription: Subscription = Subscription.EMPTY;

constructor(private dummyService: DummyService) {}

ngOnInit(): void {
    this.subscription = this.dummyService
      .getEmissions('Regular')
      .subscribe((emission) =&gt; (this.emission = emission));
}

ngOnDestroy(): void {
    this.subscription.unsubscribe();
}
}
</code></pre>
<p>为了验证代码有效我们在三秒后从<code>DOM</code>中移除这个组件</p>
<p><img src="https://miro.medium.com/max/1400/1*fFuAAdjKoMF7H5JkUpwDfQ.png" alt="image" loading="lazy"></p>
<p>如上所述, 这是最基本对取消订阅的方式, 如果我们的组件类中只有一个订阅对象(<code>Subscription</code>), 这种方式没什么问题. 但是当我们有多个订阅对象(<code>Subscription</code>)时, 针对每一个我们都需要在组件类中创建一个字段保存这个对象的的引用并在 <code>ngOnDestroy</code> 中调用 <code>unsubscribe</code>来取消订阅.</p>
<h2 id="方式二-使用-subscriptionadd-方法">方式二 使用 Subscription.add 方法</h2>
<p>RxJS的订阅类(<code>Subscription</code>)内建了 Subscription.add 方法允许我们使用单个订阅对象的实例(<code>Subscription instance</code>)来简化我们操作多个订阅对象的.</p>
<p>首先, 在组件类中使用<code>new Subscription()</code>实例化创建一个字段, 然后调用该实例的 <code>Subscription.add</code> 方法, 最后在 <code>ngOnDestroy</code> 中取消订阅.</p>
<pre><code class="language-ts">@Component({
selector: 'app-add',
template: `
    &lt;div&gt;{{ emissionA }}&lt;/div&gt;
    &lt;div&gt;{{ emissionB }}&lt;/div&gt;
`,
})
export class AddComponent implements OnInit, OnDestroy {
emissionA: string;
emissionB: string;

private subscription: Subscription = new Subscription();

constructor(private dummyService: DummyService) {}

ngOnInit(): void {
    this.subscription.add(
      this.dummyService
      .getEmissions('')
      .subscribe((emission) =&gt; (this.emissionA = emission))
    );

    this.subscription.add(
      this.dummyService
      .getEmissions('')
      .subscribe((emission) =&gt; (this.emissionB = emission))
    );
}

ngOnDestroy(): void {
    this.subscription.unsubscribe();
}
}
</code></pre>
<p>打开浏览器控制台, 我们可以看到两个订阅对象:</p>
<p><img src="https://miro.medium.com/max/1400/1*wUflYxiwIq-Irih_oRLX1A.png" alt="image" loading="lazy"></p>
<p>使用这种方式, 我们可以使用RsJS内建的方法轻松的取消订阅多个可观察对象而不必在组件类创建多个字段保存订阅对象的引用.</p>
<h2 id="方式三-asyncpipe">方式三 AsyncPipe</h2>
<p>Angular内置了许多非常有用的管道(pipe), 其中一个就是AsyncPipe. <code>AsyncPipe</code>接受一个可观察对象并在组件生命周期结束时(<code>ngOnDestroy</code>)自动取消订阅.</p>
<p>与前两个示例不同, 这里我们<strong>不需要在组件中手动取消订阅</strong>, 而是将可观察对象(<code>Observable</code>)传递个 <code>AsyncPipe</code>:</p>
<pre><code class="language-ts">@Component({
selector: 'app-async',
template: `&lt;div&gt;{{ emissions$ | async }}&lt;/div&gt;`
})
export class AsyncComponent implements OnInit {
emissions$: Observable&lt;string&gt;;

constructor(private dummyService: DummyService) {}

ngOnInit(): void {
    this.emissions$ = this.dummyService.getEmissions('');
}
}
</code></pre>
<p><img src="https://miro.medium.com/max/1400/1*1H9y-W7yN55skoeG2iIELw.png" alt="image" loading="lazy"></p>
<p>在我看来, 这是在Angular中使用可观察对象(<code>Observables</code>)最简明的方式. 你只需创建可观察对象(<code>Observables</code>)然后Angular会帮助你进行订阅和取消订阅.</p>
<h2 id="方式4-takeuntil-操作符">方式4 takeUntil 操作符</h2>
<p>RxJS包含许多有用的操作符, takeUntil就是其中之一. 像这个操作符的签名一样, takeUntil 接受一个会发出取消订阅源可观察对象通知的可观察对象(<code>notifier</code>).<br>
在我们的示例中, 我们希望在组件被销毁后发出通知, 所以我们给组件类添加一个叫 <code>componentDestroyed$</code> 的字段, 它的类型是 <code>Subject&lt;void&gt;</code>, 这个字段承担了<code>通知人(notifier)</code>的角色.<br>
然后我们只需在<code>ngOnDestroy</code>发出"通知"即可, 最终的代码像下面这样:</p>
<pre><code class="language-ts">@Component({
selector: 'app-until',
template: `&lt;div&gt;{{ emission }}&lt;/div&gt;`,
})
export class UntilComponent implements OnInit, OnDestroy {
emission: string;

private componentDestroyed$: Subject&lt;void&gt; = new Subject&lt;void&gt;();

constructor(private dummyService: DummyService) {}

ngOnInit(): void {
    this.dummyService
      .getEmissions('takeUntil')
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((emission) =&gt; (this.emission = emission));
}

ngOnDestroy(): void {
    this.componentDestroyed$.next();
}
}
</code></pre>
<p><img src="https://miro.medium.com/max/1400/1*-mmehK6LSr1QHwZmH-moJw.png" alt="image" loading="lazy"></p>
<p>与之前<code>常规</code>的方式相比, 这种方式在我们有多个订阅对象时不必在组件类中创建多个字段保存对订阅对象的引用. 我们只需在管道中加入 <code>takeUntil(componentDestroyed$)</code> 即可, 剩下的RxJS会帮我们完成.</p>
<h2 id="方式五-subsink-库">方式五 SubSink 库</h2>
<p>SubSink是Ward Bell写的一个很棒的库, 它使你可以优雅的在你的组件中取消对可观察对象的订阅.</p>
<p>首先, 通过<code>npm i subsink</code>或<code>yarn add subsink</code>安装SubSink. 然后在组件类中创建一个<code>SubSink</code>类型的字段.<br>
SubSink有两种方式, 一种是简单技术(使用<code>sink</code>属性设置器), 另一种是 数组/添加(Array/Add)技术.</p>
<p>使用简单技术只需要使用<code>sink</code>设置器属性即可. 使用_数组/添加(Array/Add)技术_的话代码类似RxJS原生的<code>Subscription.add</code></p>
<p>为每一种方式创建一个订阅对象, 我们的组件类看起来像下面这样</p>
<pre><code class="language-ts">@Component({
selector: 'app-sink',
template: `
    &lt;div&gt;{{ easyEmission }}&lt;/div&gt;
    &lt;div&gt;{{ arrayAddEmission }}&lt;/div&gt;
`,
})
export class SinkComponent implements OnInit, OnDestroy {
easyEmission: string;
arrayAddEmission: string;

private subs = new SubSink();

constructor(private dummyService: DummyService) {}

ngOnInit(): void {
    /* 使用简单技术 */
    this.subs.sink = this.dummyService
      .getEmissions('')
      .subscribe((emission) =&gt; (this.easyEmission = emission));

    /* 使用数组/添加(Array/Add)技术 */
    this.subs.add(
      this.dummyService
      .getEmissions('')
      .subscribe((emission) =&gt; (this.easyEmission = emission))
    );
}

ngOnDestroy(): void {
    this.subs.unsubscribe();
}
}
</code></pre>
<p><img src="https://miro.medium.com/max/1400/1*guFofx9EQxMOh1EhEIajtA.png" alt="image" loading="lazy"></p>
<h2 id="方式六-until-destroy-库">方式六 until-destroy 库</h2>
<blockquote>
<p>注意: 这个库在Pre Ivy Angular上行为不同, 更多信息请访问文档</p>
</blockquote>
<p>until-destroy是ngneat许多很棒的库之一, 它使用<code>UntilDestroy</code>装饰器来确认哪些字段的是订阅对象(<code>Subscriptions</code>)并在组件销毁时取消订阅它们;<br>
我们还可以不通过组件类字段, 而是使用_until-destroy_定义的叫<code>untilDestroyed</code>的RxJS操作符来取消订阅.</p>
<p>要使用它我们需要给组件类加上 <code>UntilDestroy</code> 装饰器, 然后在可观察对象管道中加入 <code>untilDestroyed</code> 操作符:</p>
<pre><code class="language-ts">@UntilDestroy()
@Component({
selector: 'app-destroyed',
template: `&lt;div&gt;{{ emission }}&lt;/div&gt; `,
})
export class DestroyedComponent implements OnInit {
emission: string;

constructor(private dummyService: DummyService) {}

ngOnInit(): void {
    this.dummyService
      .getEmissions('')
      .pipe(untilDestroyed(this))
      .subscribe((emission) =&gt; (this.emission = emission));
}
}
</code></pre>
<p><img src="https://miro.medium.com/max/1400/1*1hZgWMWAFsxLONsVIN8H7w.png" alt="image" loading="lazy"></p>
<p>总的来说, until-destroy是个非常强大的库, 他可以帮你自动取消对可观察对象的订阅.<br>
此外, until-destroy还有许多其他在本文中没有进行说明的特性, 所以赶快去看看它们的github仓库吧!</p>
<h2 id="总结">总结</h2>
<p>上面我们已经看到来许多订阅和退订可观察对象方式, 每个都各有各的优劣并且有着不同的编码风格.</p>
<p>但是最重要是不管我们选择那种方式, 我们都要<strong>保持编码风格的一致</strong></p>


</div>
<div id="MySignature" role="contentinfo">
    <p>作者:Laggage</p>
<p>出处:https://www.cnblogs.com/laggage/p/14380301.html</p>
<p>说明:转载请注明来源</p><br><br>
来源:https://www.cnblogs.com/laggage/p/14380301.html
頁: [1]
查看完整版本: [译]Rxjs&Angular-退订可观察对象的n种方式