专业的编程技术博客社区

网站首页 > 博客文章 正文

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

baijin 2024-08-15 16:59:29 博客文章 8 ℃ 0 评论

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



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


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


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


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



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


不如来试试笔者的办法。


异步管道救援?



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


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


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


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


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


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


?x
<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元素。


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


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


像职业选手一样异步


只需要从可观察对象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那样担心是否会及时取消订阅。


组件上下文可观察对象


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


为了确保上下文可观察对象每次都发出,一旦它的一个源可观察对象产生一个值,就可以使用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订阅组件模板开头的上下文可观察对象,并通过变量将发出的上下文对象传递给模板的其余部分。一旦组件被销毁,所有订阅都将被异步管道终止。



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


留言点赞关注

我们一起分享AI学习与发展的干货

如转载,请后台留言,遵守转载规范

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表