全文共2792字,预计学习时长13分钟
在一个Angular组件内处理多个可观察对象的RxJS,需要在某个地方订阅它们,更重要的是,当不再需要时可以取消订阅。
有成千上万的文章告诉我们,经常退订是多么重要。内存泄漏,性能下降,疯狂的副作用——这些都可以由一个未订阅的可观察对象触发。
那么该如何操作呢?关于如何在Angular应用程序中管理RxJS订阅的教程,网上已经有很多了。但笔者的方法一定是最干净的方法之一。不相信?那就往下看吧~
有成千上万的文章在解释如何正确退订,如何避免使用管理订阅的代码以免组件代码过载。你可以使用RxJS操作符来帮忙,也可以使用ngOnDestroy钩子、Subject或组件基类。
办法是很多的,但按照这样做,不管怎么努力,都会让组件代码有点混乱。然后组件中应用程序逻辑就与添加的一些订阅管理代码搞混了,而订阅管理代码本来是防止可怕的内存泄漏的。可读性不太好。即使是非常简单的组件看起来也有点复杂和可怕……
不如来试试笔者的办法。
异步管道救援?
只想在组件创建时订阅一些可观察对象,然后在组件被销毁时取消所有订阅。为什么要为此编写一行代码?这似乎是最常见的情况。为什么要自己处理?框架不能帮忙吗?
是的,它可以: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元素。
那么,在这种情况下,异步管道是否就失效了?你得被迫手动管理订阅?还是需要在模板中使用多个订阅?
不不不,只要知道怎么做,仍然可以使用异步管道!
像职业选手一样异步
只需要从可观察对象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学习与发展的干货
如转载,请后台留言,遵守转载规范
本文暂时没有评论,来添加一个吧(●'◡'●)