RxJS:掌握操作符
学习用Angular 5和NgRx来掌握RxJS操作符。
会议介绍
本文主要基于2018年3月在落基山Angular meetup上的演讲。非常感谢Jon Rista,他帮助完成了演示!
示例应用程序
在这篇文章中,我将引用一个NgRx《英雄之旅》应用的示例,特别是* NgRx -重构-2**分支。
你也可以克隆它,然后检出ngrx-重构-2分支:
美元Git.克隆git@github.com:蓝色/ ngrx-tour-of-heros.git美元Git.结帐ngrx-refactor-2
什么是运营商?
当我引用RXJS的运营商时,我会引用这些方法<码>可观测的类。它们是纯粹的函数,转换可观察流中的信息,创建新的可观察对象,通常基于当前的可观察对象。最重要的是,RxJS中的操作符允许使用复杂的异步代码,这些代码可以很容易地以声明的方式组合。
操作符可以分为多个类别。有创建操作符,可以从一个源(如promise或value)创建一个新的可观察对象,转换操作符将转换流中的数据,过滤操作符将作为可观察流的门。
它们是如何组合的?
所有操作符返回<码>可观测的,使它们是可包链(或丢失的,但不可遗弃)。这使我们能够在同步的管道中使用一系列操作员来构思复杂的逻辑。最后,当我们订阅输出可观察到的时候,它反过来会订阅输入可观察到的。
版本?
在撰写本文时,Angular应用程序中使用了两个常见的RxJS版本。yobet外围5.4版。x在Angular 2和Angular 4中使用,而在version 5.5中使用。RxJS中的x正在Angular 5中使用。还有一个测试版的RxJS 6,你可以看看。
如何导入?
当你能够导入所有的操作符,这不是建议的,您的用户会不高兴的。当你只使用少量的操作符时,就没有必要将所有75个以上的操作符(以及它们的符号变体)发送给你的用户。
如果您使用的是RxJS v5.4。x然后你会想要使用基于原型的导入:
进口“rxjs /添加/运营商/ switchMap”;
这将附加<码>SwitchMap.方法<码>Observable.prototype。
如果你用的是RxJS v5.5。x然后您将使用导出函数的es6风格的导入,例如:
进口{SwitchMap.}从“rxjs /运营商”;
如何进行连锁运算?
同样,答案取决于您所使用的RxJS版本。RxJS 5.5。函数引入了可让表操作符语法<码>可观测的。管()方法。
首先,让我们来看看RXJS 5.x中链接运算符的示例(和Angular 2/4):
类PostsComponent{私人用户:可观测的<用户>;ngOnInit(){这。的帖子=这。用户。地图(用户=>用户。id)。SwitchMap.(id=>这。Postsservice.。的getPosts(id));}}
正如您在上面的例子中看到的,我们使用点表示法将运算符链接在一起。
下面是一个RxJS 5.5中的管道操作符的例子。x(和Angular 5):
类PostsComponent{私人用户:可观测的<用户>;ngOnInit(){这。的帖子=这。用户。管(地图(用户=>用户。id),SwitchMap.(id=>这。Postsservice.。的getPosts(id)));}}
当使用RxJS 5.5时。我们用<码>管()方法,以相同的顺序将所有运算符传递给该方法。
角呢?
我以为你永远不会问。如果您是观察者设计模式的粉丝,反应编程和RXJS的力量,那么您就像一个角开发人员一样运气。
Angular RxJS
Angular中的所有异步事件都使用了可观察对象,它们目前是通过RxJS库实现的。最后,Angular也给了我们强大的功能<码>AsyncPipe在组件模板中轻松订阅可观察流。使用非常简单而强大。
有多少运营商?
如果你看过RxJS<码>可观测的文档您可能首先注意到:哇,有很多操作符!
是的,有很多。我会说RXJS中有大约75个运营商,因为这种写作,而不是计算签名的变体。
我该如何选择?
好问题。我经常从开发者那里听到这样的话。
首先,查看位于页面底部的“查找正确的操作符”向导rxjs主页。很多人都忽略了这一点,而这在学习操作时是非常有用的。
其次,学会看大理石图;因为这将帮助您理解您正在考虑的操作符的行为。说到这,让我们快速回顾一下大理石图。
这些图表是什么?
我们用于代表可观察流的彩色图称为“大理石图”。
在确定我们在我们的应用程序中使用的操作员或运算符时,我们可以引用大理石图表。
map ()
的<码>地图()操作员是JavaScript开发人员的一个简单的起点,因为它的表现就像<码>阵列。prototype.array ()方法:
getCharacters(的名字:字符串):可观测的<阵列<字符>>{如果(的名字。长度= = =0){返回可观测的。的((]);}返回这。charactersService。getCharacters(的名字)。管(地图(marvelresponse.=>marvelresponse.。数据。结果));}
在上面的例子中<码>getCharacters()方法使用<码>charactersService实例检索匹配的字符<码>的名字指定。此示例使用Marvel API根据用户输入的名称获取奇迹字符数组。的<码>getCharacters()方法返回一个<码>可观测的。我们使用<码>地图()运算符返回<码>结果属性,它是Marvel API返回的响应对象的一部分。该操作符使我们能够将可观察流的响应映射到另一个值,在本例中是字符结果数组。
filter ()
的<码>过滤器()运营商的表现得很像<码>阵列。prototype.filter ()方法:
过滤器(的名字:字符串):可观测的<阵列<字符>>{如果(的名字。长度= = =0){返回可观测的。的((]);}返回这。charactersService。getCharacters(的名字)。管(过滤器(marvelresponse.=>marvelresponse.。码= = =200.),地图(marvelresponse.=>marvelresponse.。数据。结果));}
在上面的例子中,我们使用<码>过滤器()操作符,只在HTTP响应的状态码为200时向可观察流的观察者发出通知。
利用()
要么<码>做()
的<码>做()操作符被重命名为<码>利用()在rxjs v5.5.x中作为升级到销售运营商的一部分,以避免与保留字的禁用<码>做(DO-WISH循环的一部分)。
的<码>利用()操作符用于执行副作用。操作符接收observable的通知,所以我们可以使用通知的值来执行一个副作用;例如,发送一个操作来更改应用程序的状态。并且,操作符总是返回它接收到的通知。换句话说,它不会转换或过滤通知(或者如果您愿意,也可以使用<码>这值)。
让我们来看一个例子:
出口类EditComponent实现了OnInit{权力:可观测的<权力>;构造函数(私人activatedRoute:ActivatedRoute,私人间小吃店:matsnackbar.,私人商店:商店<PowersState>){}ngOnInit(){这。权力=这。activatedRoute。paramMap。管(龙头(paramMap=>{常量id=+paramMap。得到(“id”);这。商店。调度(新SelectPower({id:id}))这。hasPowerInStore(id)。订阅(存在=>{如果(!存在){这。商店。调度(新LoadPower({id:id}));}});}),SwitchMap.(()=>这。商店。管(选择(getSelectedPower))));}}
这个有点长,让我们把它分解一下。
- 的<码>EditComponent一个用于编辑超能力的有状态组件(或者真的只是一个?<码>权力在MongoDB文档)。
- 的<码>权力房地产是一个<码>可观测的的<码>权力目的。
- 的<码>利用()运营商接收<码>paramMap,这是一个<码>地图,价值来自<码>ActivatedRoute.paramMap可观察到的。
- 与<码>id值,我们分派<码>SelectPower操作,当存储库不包含所选的能力时,则<码>LoadPower行动。
我们可以用<码>利用()操作员执行任务(如日志记录)或副作用(如将操作分派到存储区)。
switchMap ()
的<码>SwitchMap.()操作员从一个流切换到另一个流,取消订阅前一个可观察并返回新的可观察到。
在前面的示例中,我们使用了<码>SwitchMap.()方法返回的新observable的结果<码>getSelectedPowerselector,它返回一个被选中对象的可观察对象<码>权力目的。
的另一个常见用例<码>SwitchMap.()是一个NgRx效应:
@效果()loadPower:可观测的<行动>=这。行动。减低<LoadPower>(LOAD_POWER)。管(地图(行动=>行动。有效载荷),SwitchMap.(有效载荷=>这。powersService。得到力量(有效载荷。id)。管(重试(3.))),地图(权力=>新LoadPowerSuccess(权力)),catchError((e:HttpErrorResponse)=>可观测的。的(新HttpError(e))));
在上面的例子中<码>SwitchMap.()运营商接收<码>有效载荷通知的<码>LoadPower属性的结果,并返回一个新的可观察对象<码>得到力量()方法<码>powersService,它使用了<码>HttpClient从REST API获得强大的功能。
catchError ()
要么<码>抓住()
的<码>抓住()运营商以RXJS v5.5.x重命名为<码>catchError()。顾名思义,我们用<码>catchError()操作符来接收可观察流中发出的任何错误通知。
在前面的示例中,我们使用了<码>catchError()运算符捕获任意<码>HttpErrorResponse对象。这个例子是一个NgRx效果,所以我们返回一个new<码>HttpError作为错误结果将被分派的操作。
第()
的<码>第()操作员返回所观察到的第一个通知并完成可观察流。我们可以指定一个<码>谓词函数来筛选特定的值。我们也可以指定a<码>选择器函数转换操作符返回的值。它支持a<码>默认的价值。
当我们看<码>EditComponent前面,我们引用了<码>hasPowerInStore()方法,该方法返回一个<码>可观测的的<码>布尔基价值;要么<码>真正当能量存在于仓库时,或者<码>假商店中的电源不存在时。这是一种方便的方法来检查NGRX存储中是否存在值:
hasPowerInStore(id:数量):可观测的<布尔基>{返回这。商店。选择(getPowerEntities)。管(第一个(权力=>权力= = !空值,权力=>权力(id]= = !未定义的));}
在上面的例子中,我使用<码>第()运算符具有<码>谓词和<码>选择器指定的功能。一,是<码>谓词要求实体字典不是<码>空值。然后,这<码>选择器函数的作用是:返回<码>布尔基值,该值确定实体是否存在于存储中。
持续()
相反的<码>第()运营商,<码>持续()返回最后一个被观察到的值,并完成可观察流。另一个区别是<码>持续()运营商等待完成在返回之前从原始流中通知。这对于一次性和完成的操作非常有用:
withlattestfrom()
的<码>withlattestfrom()操作员合并数据流,提供来自的值另一个对象的最新值源一旦两个可观察对象都发出了一个通知(或值)。
让我们来看一个例子:
ngOnInit(){这。英雄=这。权力。管(FATHLATISTFROM.(这。胜利者服务。getHeroes()),地图(((权力,英雄])=>英雄。过滤器(英雄=>英雄。权力。指数(权力。id)>-1)));}
在本例中,我们检索<码>英雄有一个选择的<码>权力。的<码>withlattestfrom()操作员合并所造成的结果<码>getHeroes()可观察到的溪流<码>权力流。的<码>地图()操作符以数组的形式接收observable流发出的两个值。我们使用解构来访问数组中的第一个和第二个值,即<码>权力对象和数组<码>英雄。
forJoin ()
的<码>forkJoin()运算符类似于<码>Promise.all ()方法,因为它会同时启动(分叉)多个观察者,然后在所有可观察对象完成后连接每个可观察对象的最终值。重要的是要注意,如果任何一个输入的observable都没有完成,那么<码>forkJoin()永远不会完成。
让我们来看一个例子:
英雄:可观测的<英雄>权力:可观测的<阵列<权力>>;ngOnInit(){这。英雄=这。activatedRoute。paramMap。管(常量id=+paramMap。得到(“id”);SwitchMap.(paramMap=>这。胜利者服务。哥特罗(id));这。权力=这。英雄。管(mergeMap(英雄=>forkJoin(英雄。权力。地图(id=>这。powersService。得到力量(id)))));}
- 在上面的例子中,我们在一个组件中设置了两个属性:<码>英雄和<码>权力。
- 的<码>英雄房地产是一个<码>可观测的的<码>英雄对象,<码>权力是一个<码>可观测的数组的<码>权力对象。
- 的<码>权力属性是加载与所选英雄相关联的每个电源的结果。
- 的<码>英雄对象有一个<码>权力的数组<码>id分配给英雄的每个电源的值。
- 的<码>forkJoin()函数返回的所有可观察对象的分支<码>得到力量()方法,然后等待所有的可观察对象完成,然后返回对应可观察对象的最后一个值的数组。
mergemap()
的<码>mergemap()操作符将源observable接收到的值映射到一个返回observable的函数中,并合并该observable发出的值。
在上面的例子中,我们使用<码>mergemap()接受<码>英雄源发出的价值<码>this.hero可观察到的。然后我们使用<码>forkJoin()操作符返回数组的单个observable<码>权力对象返回的<码>得到力量()方法。然后使用该值使用<码>mergeAll ()。
distinctUntilChanged ()
的<码>distinctUntilChanged()操作员仅发出来自源可观察的不同值。它默认使用严格的平等检查,或者您可以指定一个<码>比较器函数来确定值的唯一性。
让我们来看一个例子:
ngOnInit(){这。形式。valueChanges.。管(debounceTime(500),distinctUntilChanged((上一页:权力,下一个:权力)=>上一页。的名字= = =下一个。的名字))。订阅(值=>{如果(!这。形式。有效的){返回;}这。powerChange。发射({…这。权力,…值});});}
在本例中,我们只想发出<码>powerChange事件时的名称<码>权力对象的形式发生了变化。的<码>distinctUntilChanged()允许我们过滤可观察流,并只在我们确定值在可观察流中发生了变化时才发出该值。这有助于我们避免保存相同的数据,比如,用户与表单值交互,但随后又将其更改回原始值。
debounceTime ()
在前面的示例中,我们还使用了<码>debounceTime()操作符等待,直到从可观察流发出的最后一个值已经过去500毫秒。使用<码>debounceTime()运算符可以避免发出<码>powerChange事件在输入的每个按键上。如果一个新值在间隔过期之前到达,则删除之前发出的值。
结论
首先,使用RxJS库的响应式编程是处理异步事件或数据的可观察流的范例。使用操作符来过滤、转换或改变数据流的时间,使我们能够轻松地用可观察流组合逻辑。
其次,如果您是一个角开发人员,对RXJS中可用的基本操作员有了很好的理解,将大大提高您创建消耗并创建异步事件和值的应用程序的能力。在用角度构建应用程序时,几乎不可能使用RXJS和可观察。
最后,学会阅读和理解大理石图对于了解各种操作人员是非常有益的。