欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

教你如何用Angular更好地管理RxJS订阅

程序员文章站 2024-03-18 22:46:34
...

全文共2792字,预计学习时长13分钟

 

教你如何用Angular更好地管理RxJS订阅

图源:unsplash

 

在一个Angular组件内处理多个可观察对象的RxJS,需要在某个地方订阅它们,更重要的是,当不再需要时可以取消订阅。

 

有成千上万的文章告诉我们,经常退订是多么重要。内存泄漏,性能下降,疯狂的副作用——这些都可以由一个未订阅的可观察对象触发。

 

那么该如何操作呢?关于如何在Angular应用程序中管理RxJS订阅的教程,网上已经有很多了。但笔者的方法一定是最干净的方法之一。不相信?那就往下看吧~

 

教你如何用Angular更好地管理RxJS订阅

 

有成千上万的文章在解释如何正确退订,如何避免使用管理订阅的代码以免组件代码过载。你可以使用RxJS操作符来帮忙,也可以使用ngOnDestroy钩子、Subject或组件基类。

 

教你如何用Angular更好地管理RxJS订阅

图源:unsplash

 

办法是很多的,但按照这样做,不管怎么努力,都会让组件代码有点混乱。然后组件中应用程序逻辑就与添加的一些订阅管理代码搞混了,而订阅管理代码本来是防止可怕的内存泄漏的。可读性不太好。即使是非常简单的组件看起来也有点复杂和可怕……

 

不如来试试笔者的办法。

 

教你如何用Angular更好地管理RxJS订阅

 

异步管道救援?

 

教你如何用Angular更好地管理RxJS订阅

图源:unsplash

 

只想在组件创建时订阅一些可观察对象,然后在组件被销毁时取消所有订阅。为什么要为此编写一行代码?这似乎是最常见的情况。为什么要自己处理?框架不能帮忙吗?

 

是的,它可以:Angular的异步管道。

 

可以在组件模板中使用异步管道。将可观察对象传递给异步管道,然后订阅,返回值,最后当组件被破坏时取消订阅。至少乍一看,这就足够了。

 

假设有一个可观察对象lastComment$返回最后一个评论的对象。假设要在模板中打印注释内容。可以这样做:

 

<h1>Lastcomment</h1>
<div>{{ (lastComment$ | async).body }}</div>

 

现在看来一切都还不错。但如果还想添加评论主题呢

 

<h1>Lastcomment</h1>
<h2>{{ (lastComment$ | async).subject }}</h2>
<div>{{ (lastComment$ | async).body }}</div>

 

emmm…不再那么酷了,订了两份而不是一份。每次使用异步管道都会创建一个单独的订阅(即便使用相同的可观察对象)。

 

想象一下,在可观察对象lastComment$下,隐藏了对API或一些复杂数据处理的HTTP请求。对于两个单独的订阅,需要执行两次而不是一次操作。听起来有点麻烦,那么该怎样做呢?

 

可以利用*ngIf指令来帮助自己。用div元素封装所有内容。添加*ngIf并在其中订阅lastcoment$。

 

<div*ngIf="lastComment$ | async">
...
</div>

 

每当发出一条评论,都会显示div内容。如果没有任何评论,整个div都将被隐藏。是不是很完美?

 

可以将异步管道结果保存在变量下,并在*ngIf显示的元素中使用这个变量。这样的操作你可能见过很多次:调用comment变量。

 

<div*ngIf="(lastComment$ | async) as comment">
<h1>Last comment</h1>
<h2>{{ comment.subject}}</h2>
<div>{{ comment.body}}</div>
</div>

 

看起来已经搞定了,对吧?还没完!

 

现在想象一下组件模板有点大,评论数据显示在两个或多个位置,并用其他内容分隔。假设要打印评论作者的姓名,然后是一篇博客文章,然后是所有评论的详细信息,该怎么做?如下所示:

 

<div *ngIf="(lastComment$ | async) as comment">
                     This was recently commented by {{comment.author }}!
</div>
<div>
<h1>Post</h1>
<h2>{{blogPost.subject }}</h2>
<div>{{ blogPost.body}}</div>
</div>
<div *ngIf="(lastComment$ | async) as comment">
<h1>Last comment</h1>
<h2>{{comment.subject }}</h2>
<div>{{ comment.body}}</div>
</div>

rawlastcoment.js|GitHub

 

又是有多个订阅!继续封装di还有用吗?不会又起作用?看看:

 

<div *ngIf="(lastComment$ | async) as comment">
<div>
                       This was recently commented by {{comment.author }}
</div>
<div>
<h1>Post</h1>
<h2>{{blogPost.subject }}</h2>
<div>{{ blogPost.body}}</div>
</div>
<h1>Last comment</h1>
<h2>{{comment.subject }}</h2>
<div>{{ comment.body}}</div>
</div>

rawlastcoment.js | GitHub

 

它不再有用了,至少不总是这样。

 

注意:如果根本没有lastComment,则异步管道返回null。然后,*ngIf指令隐藏整个div及其所有内容,以及保持可见的无关blogPost元素。

 

那么,在这种情况下,异步管道是否就失效了?你得*手动管理订阅?还是需要在模板中使用多个订阅?

 

不不不,只要知道怎么做,仍然可以使用异步管道!

 

教你如何用Angular更好地管理RxJS订阅

 

像职业选手一样异步

 

只需要从可观察对象lastComment$创建一个新的可观察对象,并确保它总是返回一个真实的值。稍微转换一下可观察对象lastComment$,并将其称为componentContext$。

 

letcomponentContext$ = this.lastComment$.pipe(
  map(lastComment => ({ lastComment}))
);

 

完成了!请注意,新的可观察对象总是返回一个真实的值(对象),即使lastcoment$发出null。如果为空,则返回值如下:{lastComment:null}。来看看它的表现吧:

 

<div *ngIf="(componentContext$ | async) as ctx">
<div *ngIf="ctx.lastComment as comment">
                            This was recently commented by {{comment.author }}
</div>
<div>
<h1>Post</h1>
<h2>{{blogPost.subject }}</h2>
<div>{{ blogPost.body}}</div>
</div>
<div *ngIf="ctx.lastComment as comment">
<h1>Last comment</h1>
<h2>{{comment.subject }}</h2>
<div>{{ comment.body}}</div>
</div>
</div>

rawcomponentContext.js | GitHub

 

一切正常。封装div始终可见,因为componentContext$发出的值总是真实的。显示或隐藏内部div取决于lastComment的值。检查相应的内部*ngIfs中的ctx.lastcoment属性并作出适当反应。

 

看看它看起来有多干净:有一个异步管道、一个订阅和一个完全工作的组件。

 

教你如何用Angular更好地管理RxJS订阅

图源:unsplash

 

而且所有订阅现在都由异步管道本身控制,不必像Angular那样担心是否会及时取消订阅。

 

教你如何用Angular更好地管理RxJS订阅

 

组件上下文可观察对象

 

在上面创建的可观察对象——组件上下文可观察对象,它是一个由组件使用的所有可观察对象组成的单一可观察对象。它发出组件上下文对象,其中包含组件需要正确呈现的所有数据。

 

为了确保上下文可观察对象每次都发出,一旦它的一个源可观察对象产生一个值,就可以使用CombineTest运算符创建它。

 

一般来说,可以这样创建:

 

@Component({...})
                exportclassExampleComponent implements OnInit {
                ngOnInit() {
                  ...
this.context$=combineLatest(
this.blogPost$,
this.lastComment$
                  ).pipe(
                    map(([blogPost, lastComment]) => {
return { blogPost, lastComment };
                    })
                  );
                  ...
                }
                }

rawExampleComponent.js | GitHub

 

一旦配置好,就可以像这样使用:

 

<ng-container *ngIf="(context$ | async) as ctx">
<!--componentbodybelow-->
<div *ngIf="ctx.lastComment as comment">
                   This was recently commented by {{comment.author }}
</div>
<div *ngIf="ctx.blogPost as blogPost">
<h1>Post</h1>
<h2>{{blogPost.subject }}</h2>
<div>{{ blogPost.body}}</div>
</div>
<!--componentbodyabove-->
</ng-container>

rawcontext.js | GitHub

 

构建在组件中管理RxJS可观察对象的方式,可以总结为以下三条规则,在整个应用程序中都可应用:

 

1.不要直接在组件代码中订阅可观察对象。如果不需要,则不必管理它们的订阅。需要订阅组件中的可观察对象时,去使用异步管道。

 

2.创建组件上下文可观察对象,它由要在组件中使用的可观察对象组成。确保它总是发出真实的价值。

 

3.使用异步管道和*ngIf订阅组件模板开头的上下文可观察对象,并通过变量将发出的上下文对象传递给模板的其余部分。一旦组件被销毁,所有订阅都将被异步管道终止。

 

教你如何用Angular更好地管理RxJS订阅

图源:unsplash

 

这个最干净的解决方案,你学会了吗?

 

教你如何用Angular更好地管理RxJS订阅

留言 点赞 关注

我们一起分享AI学习与发展的干货
欢迎关注全平台AI垂类自媒体 “读芯术”

教你如何用Angular更好地管理RxJS订阅

(添加小编微信:dxsxbb,加入读者圈,一起讨论最新鲜的人工智能科技哦~)