RxJS 夯实基础 | 想要的这里都有

2,717 阅读24分钟

RxJS.png

一、简介

从 2023 年 NestJS 的学习与实践中发现,RxJS 在 NestJS gRPC 微服务中扮演重要角色,gRPCproto 文件生成 TS 类型文件式,使用可观察对象,而不是 Promise。

面对 NestJS 和 Angular 系列,如果对 RxJS 不熟悉,就像多年前都端开发者不熟悉 webpack 一样。

多年以后回顾 webpack, 学习 RxJS 的场景就跟以前简直一模一样。

为了更好的学习并全面的掌握 RxJS, 本文以 RxJS 基础为核心用有文字图片、配合视频方式讲解,希望能够帮助到大家。

RxJS 是一个函数式的响应式可观察对象编程库。

二、可观察对象基础

可观察对象Observable类.png

RxJS 中可观察对象 Observable最基础 的内容,没有学习过 RxJS 也不用担心,可观察对象本身只是一个,很简单并且很少直接使用。

我们需要知道的是:可观察对象会创建 数据流。(如果你更加习惯观看视频,可以观看以下的视频,关于 RxJS 可观察对象的讲解。)

三、pipe 函数

pipe可观察对象构造函数 Observable 的实例方法。

pipe 函数很好理解:是数据流的操作管道。在管道中,可以调用 操作符函数 处理流入的数据。

四、操作符

操作符是 RxJS 中最难得部分,难在什么地方?

使用场景多,操作符多,需要基础牢固和实战经验,才能灵活运用 RxJS

RxJS Operators.png

五、创建型操作符

创建型操作符(15).png

创建型操作符:是 RxJS 内置一些常用的操作符函数,方便日常使用的操作符。需要注意的它调用函数的返回值是 可观察对象,像常见的内容有哪些。

✅创建型操作符一共包含 15 个操作符

六、Join 创建型操作符

Join 创建型操作符.png

创建型操作符一共包含 7 个操作符

操作符说明观看视频
🍭combineLatestcombineLatest 用于将多个 Observables 的最新值合并成一个新的值,并且当其中任何一个 Observable 发出新值时都会触发这个合并。
🍭concatconcat 用于将多个 observables 按顺序串联起来。它会按照顺序订阅并依次发出每个 observable 的值,只有前一个 observable 完成(complete)后,才会订阅下一个 observable
🍭forkJoinforkJoin 是一个 RxJS 中的操作符,它可以将多个 Observable 组合成一个新的 Observable,等待所有的 Observable 完成,然后将它们的最新值作为数组发出。
🍭mergemerge 可以将多个 Observable 流合并成一个。它会同时订阅所有的 Observable,然后按照它们发出值的顺序将这些值合并成一个单一的 Observable 流。
🍭partitionpartition 用于将 Observable 拆分为两个 Observables,一个满足特定条件,另一个不满足该条件。
🍭racerace 允许你在多个 Observable 中进行竞速。它会观察多个 Observables,并只发出首先发出值或完成的 Observable 的数据。
🍭zipzip 用于将多个 Observables 的数据配对合并成一个新的 Observable。这个新的 Observable 发出的数据是由每个输入 Observable 相应位置的数据配对而成的数组。

七、转换类型操作符

转换类型操作符.png

✅转换型操作符一共有 28 个操作符

7.1) 🍀buffer 系列

操作符说明
🍭bufferbuffer 允许你根据另一个 Observable 的信号来决定何时发出缓冲区的数组。它接受一个 closingNotifier 参数,当这个 closingNotifier 发出信号时,buffer 会发出当前缓冲区中的值并重新开始缓冲
🍭bufferCountbufferCount 允许你在源 Observable 发出指定数量的值后,将这些值作为数组发出。
🍭bufferTimebufferTime 允许你在一定时间间隔内收集源 Observable 发出的值,并在每个时间间隔结束时将收集到的值作为数组发出
🍭bufferTogglebufferToggle 允许你通过打开和关闭缓冲区来控制何时发出缓冲区的值。它接受两个参数,一个用于打开缓冲区的 Observable,另一个用于关闭缓冲区的 Observable。
🍭bufferWhenbufferWhen 允许你通过一个函数来动态地决定何时打开和关闭缓冲区。这个函数返回一个 Observable,当它发出信号时,缓冲区会发出当前收集到的值数组并重新开始缓冲。

7.2) 🍀window 系列

操作符说明
🍭windowwindow 操作符将数据流拆分成一系列窗口,每个窗口包含指定数量的项。它创建一个 Observable,该 Observable 发出这些窗口。
🍭windowCountwindowCount 操作符与 window 类似,但是它是基于项的数量来创建窗口,而不是基于时间。
🍭windowTimewindowTime 操作符创建一个 Observable,该 Observable 定期发出时间窗口,每个窗口包含在指定时间内发出的项。
🍭windowTogglewindowToggle 操作符通过使用两个 Observables 控制窗口的开启和关闭。一个 Observable(开启 Observable)用于确定何时开始一个新窗口,另一个 Observable(关闭 Observable)用于确定何时关闭当前窗口。
🍭windowWhenwindowWhen 操作符创建一个 Observable,该 Observable 在由另一个 Observable 提供的信号时开启新窗口。

7.3) 🍀switch 系列

操作符说明
🍭switchMapswitchMap 操作符用于将 Observable 的每个项映射为一个新的 Observable,然后仅发出最新映射产生的 Observable 的项,而忽略之前的映射。
🍭switchMapToswitchMapTo 操作符与 switchMap 类似,但它将每个源项映射为一个固定的 Observable(而不是基于源项的映射函数),然后只发出这个固定 Observable 的项。
🍭switchScanswitchScan 操作符是 scanswitchMap 的结合。它接受一个累加器函数和一个映射函数,将每个源项应用于累加器函数,得到一个中间累加值,然后将中间累加值传递给映射函数,映射函数返回一个 Observable。然后,它会取消订阅先前的 Observable(如果有的话),并订阅新的 Observable。这个操作符通常用于处理状态的累积和切换。

7.4) 🍀merge 系列

操作符说明
🍭mergeMapmergeMap 操作符用于将每个源项映射为一个 Observable,然后将这些 Observables 并行合并成一个 Observable。它可以处理多个 Observable 的同时发射,并不会取消之前的 Observables。
🍭mergeMapTomergeMapTo 操作符与 mergeMap 类似,但它将每个源项映射为一个固定的 Observable(而不是基于源项的映射函数),然后将这些 Observables 并行合并成一个 Observable。
🍭mergeScanmergeScan 操作符是 scanmergeMap 的结合。它接受一个累加器函数和一个映射函数,将每个源项应用于累加器函数,得到一个中间累加值,然后将中间累加值传递给映射函数,映射函数返回一个 Observable。然后,它会将这些 Observables 并行合并成一个 Observable。

7.5) 🍀concat 系列

操作符说明观看视频
🍭concatMapconcatMap 操作符用于将每个源项映射为一个 Observable,然后按顺序连接这些 Observables,一个接一个地发出它们的项。它确保前一个 Observable 完成后,才会订阅和发出下一个 Observable。
🍭concatMapToconcatMapTo 操作符与 concatMap 类似,但它将每个源项映射为一个固定的 Observable(而不是基于源项的映射函数),然后按顺序连接这些 Observables。

7.6) 🍀exhaust 系列

操作符说明
🍭exhaustexhaust 操作符用于防止并发 Observable 的订阅。如果当前存在活跃的 Observable(第一个 Observable 还未完成),则会忽略后续的 Observable,直到第一个 Observable 完成。
🍭exhaustMapexhaustMap 操作符是 exhaust 的映射版本。它将每个源项映射为一个 Observable,然后只有当当前没有活跃的 Observable 时才会订阅和发出该 Observable。如果当前有活跃的 Observable,它会忽略新的 Observable。这个操作符通常用于确保在某个条件下只处理一个 Observable,而忽略其他触发条件。

7.7) 🍀map 系列

操作符说明
🍭mapmap 操作符用于映射 Observable 中的每个项。你可以提供一个函数,该函数将每个源项映射为一个新的项,并在新的 Observable 中发出。
🍭mapTomapTo 操作符将 Observable 中的每个项映射为一个固定的值。不像 map 需要一个函数进行映射,mapTo 直接指定一个值,然后在新的 Observable 中发出该值。

7.8)🍀merge 系列

操作符说明
🍭mergeMapmergeMap 操作符用于将每个源项映射为一个 Observable,然后将这些 Observables 并行合并成一个 Observable。它可以处理多个 Observable 的同时发射,并不会取消之前的 Observables。
🍭mergeMapTomergeMapTo 操作符与 mergeMap 类似,但它将每个源项映射为一个固定的 Observable(而不是基于源项的映射函数),然后将这些 Observables 并行合并成一个 Observable。
🍭mergeScanmergeScan 操作符是 scanmergeMap 的结合。它接受一个累加器函数和一个映射函数,将每个源项应用于累加器函数,得到一个中间累加值,然后将中间累加值传递给映射函数,映射函数返回一个 Observable。然后,它会将这些 Observables 并行合并成一个 Observable。

7.9)🍀其他

操作符说明观看视频
🍭expandexpand 操作符递归地将每个源项映射为一个 Observable,并在新的 Observable 中继续应用同样的映射函数。这样就形成了一个递归的数据流,直到满足某个条件为止。
🍭groupBygroupBy 操作符将源 Observable 中的项按照某个标准进行分组,每个分组形成一个新的 Observable。它创建一个 Observable,该 Observable 发出包含分组信息的 GroupedObservable 对象。
🍭pairwisepairwise 操作符将源 Observable 中相邻的两个项组合成一个二元组,然后发出这个二元组。
🍭partitionpartition 操作符将源 Observable 拆分为两个 Observables,根据提供的条件函数决定每个项属于哪个 Observables。
🍭pluckpluck 操作符用于提取 Observable 中每个项的指定属性的值,并将这些值发出。
🍭scanscan 操作符在源 Observable 发出项时递归地应用一个累加器函数,并发出累加的结果。它类似于 Array.prototype.reduce

八、过滤类型操作符

过滤类型操作符.png

✅过滤类型操作符一共有 25 个操作符。

8.1)🍀audit

操作符说明
🍭auditaudit 用于在 Observable 流中定期采样一个辅助 Observable,并将该时刻的最新数据传递给下游。这有助于过滤掉高频产生值的情况,只保留在采样时刻最新的值。
🍭auditTimeauditTime 用于在 Observable 流中定期采样值,并将最新的值传递给下游。与 audit 操作符类似,不同之处在于 auditTime 会定期固定时间间隔内采样值。

8.2) 🍀debounce

操作符说明
🍭debouncedebounce 用于限制在一定时间内发生的事件数量,确保只有在指定的时间间隔内没有新事件发生时,才会将最后一个事件传递给观察者。
🍭debounceTimedebounceTime 作用是在指定的时间间隔内,只发出最后一次事件,忽略在这段时间内发生的其他事件。这对于处理频繁触发的事件,如用户输入、滚动等,可以有效地减少处理的次数,提高性能。

8.3) 🍀distinct

操作符说明
🍭distinctdistinct 操作符用于过滤掉源 Observable 中重复的元素,只保留首次出现的元素。
🍭distinctUntilChangeddistinctUntilChanged 是 RxJS 中的一个操作符,它用于确保 Observable 连续发出的值不重复。它会过滤掉与前一个值相同的连续重复值,只发出发生变化的值。与 distinct 不同的是,distinctUntilChanged 只关心前后两个值是否相同,而不考虑整个 Observable 中的唯一性。
🍭distinctUntilKeyChanged返回一个 Observable,它发出由源 Observable 发出的所有项目,这些项目与前一个项目相比是不同的,使用通过使用提供的键访问的属性来检查这两个项目是否不同。

8.4) 🍀first-last

操作符说明
🍭firstfirst 操作符用于从源 Observable 中获取满足条件的第一个元素,并在获取到第一个元素后立即完成 Observable。如果没有满足条件的元素,可以选择提供一个默认值,或者抛出一个错误。
🍭lastlast 操作符用于从源 Observable 中获取满足条件的最后一个元素,并在获取到最后一个元素后立即完成 Observable。如果没有满足条件的元素,可以选择提供一个默认值,或者抛出一个错误。

8.5)🍀sample 系列

操作符说明
🍭simplesample 操作符用于定期从源 Observable 中发出一个值,该值是在另一个 Observable(称为采样 Observable)发出值的时候捕获的。这有助于控制事件流的频率。
🍭simpleTimesampleTime 是指定的时间间隔定期对源 observable 进行采样,并发出最近的值

8.6)🍀skip 系列

操作符说明
🍭skipskip 用于跳过 Observable 中指定数量的值的操作符。它会忽略源 Observable 发出的前几个值,只关注之后的值。
🍭skipLastskipLast 用于跳过 Observable 中指定数量的最后几个值的操作符。它与 skip 操作符相似,但是是跳过 Observable 的尾部值。
🍭skipUntilskipUntil 用于跳过 Observable 中的值,直到指定的另一个 Observable 发出值时才开始接收源 Observable 值的操作符。
🍭skipWhileskipWhile 用于跳过 Observable 中的值,直到某个条件不再满足时才开始接收源 Observable 值的操作符。

8.6)🍀take 系列

操作符说明
🍭taketake 操作符用于从源 Observable 中取得指定数量的值,然后完成 Observable。它是一种有限操作符,通常用于限制 Observable 发出的元素数量。
🍭takeLasttakeLast 操作符用于从源 Observable 中取得指定数量的最后几个值,然后完成 Observable。这是一个有限操作符,通常用于获取 Observable 中的末尾元素。
🍭takeUntiltakeUntil 操作符用于在另一个 Observable 发出值时,终止源 Observable。这是一种用于管理 Observable 生命周期的操作符。
🍭takeWhiletakeWhile 操作符用于在满足特定条件的情况下,从源 Observable 中取得值,然后完成 Observable。这是一种有限操作符,它允许你根据条件来控制取值的过程。

8.7) 🍀throttle

操作符说明
🍭throttlethrottle 操作符用于在一段时间内只接收第一个值,然后忽略后续的值,以控制事件的触发频率。这对于处理用户输入、滚动事件等场景非常有用。
🍭throttleTimethrottleTime 操作符是 RxJS 中用于限制事件流的触发频率的操作符。它会在指定的时间间隔内,只接收第一个值,然后忽略在该时间间隔内的所有其他值。

8.8) 🍀其他

操作符说明观看视频
🍭elementAtelementAt 操作符用于从源 Observable 中获取指定索引位置的元素,并将其作为单一元素的 Observable 发出。索引位置是从 0 开始计数的。
🍭ignoreElementsignoreElements 操作符用于忽略源 Observable 发出的所有元素,只关注 Observable 的终止状态(complete 或 error)。它通常用于处理只关心 Observable 是否完成的场景。
🍭singlesingle 操作符用于在源 Observable 中,确保只有一个满足指定条件的元素,然后完成 Observable。如果满足条件的元素数量不是一个,single 操作符会抛出一个错误。

九、Join 操作符

Join 操作符.png

Join 型操作符包含 7 个操作符

操作符说明
🍭combineLatestAll用于将多个 observable 的最新值组合成一个数组,并在任一 observable 发出新值时触发。
🍭concatAll用于按顺序连接多个 observable
🍭exhaustAll用于在一个 observable 处于活跃状态时,忽略其他 observable 的值,直到该 observable 完成。
🍭mergeAll用于将多个 observable 的值合并成一个单一的 observable。
🍭switchAll用于将高阶 observable 打平(降维)成一阶 observable,并只订阅最新的内部 observable。
🍭startWith用于在 observable 发出值之前插入指定的起始值。
🍭withLatestFrom用于将一个 observable 与其他 observables 的最新值合并,并在源 observable 发出值时触发。

十、错误处理操作符

✅错误型操作符包含 3 个操作符

10.1) 🍀retry 系列操作符

操作符说明
🍭retry 操作符retry 用于在Observable发生错误时进行重试。当Observable抛出错误时,retry将重新订阅原始Observable,从而给予它另一次执行的机会。
🍭retryWhen 操作符retryWhen 允许你更灵活地控制何时进行重试。与retry不同,retryWhen接受一个函数作为参数,该函数返回一个新的Observable。当原始Observable发生错误时,retryWhen会调用这个函数,并且只有当这个新的Observable发出值时,它才会进行重试。(已经不推荐使用)

10.2) 🍀其他

操作符说明视频
🍭catchErrorcatchError 是 RxJS 中的一个操作符,用于捕获 observable 中的错误,并提供一个备用的 observable 或值来处理错误。这个操作符通常用于处理 observable 中的异常情况。

十一、工具操作符

工具型操作符.png

✅工具型操作符包含 12 个操作符

11.1) 🍀delay 系列

操作符说明
🍭delaydelay 操作符是 RxJS 中用于引入时间延迟的一种操作符。它的主要目的是推迟 observable 发出的数据,以便在一定的时间间隔之后再将数据传递给订阅者。
🍭delayWhendelayWhen 操作符是 RxJS 中用于引入动态延迟的操作符。它允许你根据每个值的情况动态地设置延迟时间,而不是像 delay 操作符那样使用固定的延迟时间。

11.2) 🍀materilize-dematerilize 系列

操作符说明
🍭dematerilizedematerialize 操作符用于将源 observable 发出的 Notification 对象转换回它们原始的通知类型。通常,当使用 materialize 操作符将 observable 的通知转换为 Notification 对象后,可以使用 dematerialize 恢复原始的通知类型。
🍭materilizematerialize 操作符用于将 observable 的每个通知(包括 next、error 和 complete)转换为一个特殊的 Notification 对象。这个对象包含了原始通知的类型以及对应的值或错误信息。

11.3) 🍀observeOn 和 subscribeOn 系列

操作符说明
🍭observeOnobserveOn 操作符是 RxJS 中用于控制 observable 在哪个调度器上执行的操作符。它允许你指定在哪个上下文中订阅和观察 observable,以及处理它们发出的数据。
🍭subscribeOnsubscribeOn 操作符是 RxJS 中用于指定 observable 在哪个调度器上进行订阅的操作符。它影响 observable 的订阅阶段,而不是数据发出阶段。

11.4) 🍀time 系列

操作符说明
🍭timeIntervaltimeInterval 操作符是 RxJS 中的一个用于测量两个连续数据发出之间的时间间隔的操作符。它会将 observable 发出的每个数据包装成一个对象,该对象包含原始数据和与上一个数据发出之间的时间间隔。
🍭timestamptimestamp 操作符是 RxJS 中的一个用于给每个发出的数据项附加时间戳的操作符。它将 observable 发出的每个数据包装成一个对象,该对象包含原始数据和该数据的时间戳。
🍭timeouttimeout 操作符是 RxJS 中的一个用于设置超时的操作符。它可以用来限制 observable 在规定时间内完成,否则会引发超时错误。
🍭timeoutWithtimeoutWith 操作符是 RxJS 中的一个用于处理超时情况并提供备用 observable 的操作符。它允许你在 observable 超时时切换到另一个备用的 observable。

11.5) 🍀其它

操作符说明观看视频
🍭taptap 用于在Observable的生命周期中拦截并执行一些额外的操作,而不会对Observable的值产生影响。它对于调试、记录日志或执行任何不会改变Observable的操作非常有用。
🍭toArraytoArray 用于将 observable 发出的所有数据收集到一个数组中的操作符。它会等待 observable 完成(发出 complete 通知)后,将所有的 next 通知的值组合成一个数组,然后发出这个数组.

十二、条件和布尔类型操作符

条件和布尔类型.png

✅条件和布尔型操作符包含 5 个操作符

12.1) 🍀find 系列

操作符说明
🍭findfind 用于在 observable 中找到第一个满足条件的数据项的操作符。它会在找到满足条件的数据项后发出该项,并完成 observable。
🍭findIndexfindIndex 用于找到第一个满足条件的数据项的索引的操作符。它会在找到满足条件的数据项后发出该项的索引,并完成 observable。

12.2) 🍀其他

操作符说明视频
🍭defaultIfEmptydefaultIfEmpty 用于在 observable 为空时提供默认值的操作符。它会检查 observable 是否发出了任何数据,如果没有,则发出指定的默认值。
🍭everyevery 用于判断 observable 中所有数据项是否都满足指定条件的操作符。它会在 observable 完成时发出一个布尔值,表示所有数据项是否都满足条件。
🍭isEmptyisEmpty 用于检查 observable 是否为空的操作符。它会在 observable 完成时发出一个布尔值,表示 observable 是否为空(没有发出任何数据项)。

十三、数学和聚合操作符

数学和聚合操作符.png

✅数学和聚合操作符符包含 4 个操作符

操作符说明视频
🍭countcount 操作符用于统计源 Observable 发出的项的数量,并在完成时发出该数量。它返回一个新的 Observable,该 Observable 只发出一个项,即源 Observable 发出的项的数量。
🍭maxmax 操作符用于找到源 Observable 发出的项中的最大值,并在完成时发出该最大值。它返回一个新的 Observable,该 Observable 只发出一个项,即源 Observable 中的最大值。
🍭minmin 操作符用于找到源 Observable 发出的项中的最小值,并在完成时发出该最小值。它返回一个新的 Observable,该 Observable 只发出一个项,即源 Observable 中的最小值。
🍭reducereduce 操作符用于递归地应用一个累加器函数来处理源 Observable 发出的每个项,并在完成时发出最终的累加结果。它返回一个新的 Observable,该 Observable 只发出一项,即最终的累加结果。

十四、主题

主题.png

操作符说明
🍭Subject一种同时充当观察者和可观察对象的特殊类型。它是热 Observable,开始时不会发射值,而是在有观察者订阅后才开始发射值。
🍭BehaviorSubject在创建时需要初始值,会记住最新的值,并在有新的观察者订阅时立即将这个值推送给观察者。
🍭ReplySubject会记住 Observable 发射的所有值,并在有新的观察者订阅时将这些值全部发送给观察者。
🍭AysncSubject只有当 Observable 完结时才会将最后一个值发送给观察者。
🍭Void Subject无值的 Subject,即不传递任何值给观察者。

十五、调度

RxJS 调度.png

操作符说明
🍭null过不传递任何调度程序,通知将同步且递归地传递。将此用于恒定时间运算或尾递归运算。
🍭queueSchedulerqueueScheduler 是 RxJS 中的一个调度器,用于在当前任务队列结束时调度任务。它会按照顺序执行任务,确保任务按照添加的顺序执行。
🍭asapSchedulerasapScheduler 是 RxJS 中的调度器之一,表示尽快调度任务,但会优先于 queueScheduler 执行。
🍭asyncSchedulerasyncScheduler 是 RxJS 中的调度器,它使用 setTimeoutsetInterval 来安排任务的执行。它提供了异步调度的能力。
🍭animationFrameScheduleranimationFrameScheduler 是 RxJS 中专门用于与浏览器的 requestAnimationFrame 配合的调度器。它可用于实现与动画相关的任务调度。

十六、多播

多播.png

16.1)🍀v8 版本

ConnectableObservable 对象在 v8 版本中将被废弃,推荐使用 connectable 进行链接。refCount 推荐使用 share 操作符。

16.2)🍀新多播 API

操作符说明
🍭connectable创建一个可观察对象,一旦connect()调用它,就会进行多播。
🍭connect通过在一个函数中多播源来创建一个Observable,该函数允许开发人员在连接之前定义多播的用法。
🍭share返回一个新的Observable,它多播(共享)原始Observable。只要至少有一个订阅者,这个Observable就会被订阅并发出数据。当所有订阅者都取消订阅时,它将取消订阅源Observable。因为Observable是多播的,所以它使流 hot 。这是 multicast(() => new Subject()), refCount() 的别名。
🍭shareReply共享源和重播订阅时指定数量的排放。

16.3)🍀多播适用场景

多播通常用于处理计算成本高昂的操作,如网络请求或定时器。

16.4)🍀多播与主题的不同

  • 多播用于在多个订阅者之间共享同一组数据,以提高性能。
  • 主题用于手动推送值给订阅者,每个订阅者可以接收到不同的值。

16.5)🍀多播基础对象转换

  • 🍭ConnectableObservable -> connectable
  • 🍭refCount -> share

16.6)🍀多播

  • 🍭multicast -> connectable
  • 🍭multicast + subject -> connectable(resetOnDisconnect) + subject
  • 🍭multicast + subject + refCount -> share
  • 🍭multicast 和选择器 -> connect

16.7)🍀publish 转换

  • 🍭publish -> connectable(resetOnDisconnect/connector)
  • 🍭publish + refCount -> share
  • 🍭publish 和选择器 -> connect

16.8)🍀publishBehavior 转换

  • 🍭publishBehavior -> connectable + BehaviorSubject
  • 🍭publishBehavior + refCount -> share + BehaviorSubject

16.9)🍀publishLast 转换

  • 🍭publishLast -> connectable + AsyncSubject
  • 🍭publishLast + refCount -> share + AsyncSubject

16.10)🍀publishReply 转换

  • 🍭publishReplay -> connectable + ReplaySubject
  • 🍭publishReplay + refCount -> share + ReplaySubject
  • 🍭publishReplay + 选择器 -> connect + ReplaySubject

十七、测试

测试.png

17.1)🍀简介 Marble Testing

Marble Testing 使用一种被称为 "marbles" 的可视化语言,它可以帮助开发者更直观地理解和测试 Observable 的行为。

17.2)🍀Marble Diagram

用于展示 Observables 在时间轴上的行为,在 Diagram 中时间从左向右逐渐增加

17.3)🍀Marble 语法和时间线推进语法

字符含义
' '水平空格,被忽略,可用于垂直对齐多个 Marble Diagrams
'-'表示虚拟时间的一帧(frame)
[0-9]+[ms|s|m]时间的推进,数字后可跟单位 ms(毫秒)、s(秒)、m(分钟),例如 a 10ms b 表示时间推进 10 毫秒
'|'表示 Observable 成功完成,即 producer 发送 complete() 信号
'#'表示 Observable 发生错误,即 producer 发送 error() 信号
[a-z0-9]表示 producer 发送的值,通过 next() 发射
'()'表示同一帧中的多个事件,用于同步地发送多个值或完成/错误信号。例如,(abc) 表示在同一帧中发射值 a、b、c,然后虚拟时间向前推进 (abc).length 个 frames
'^'订阅点,用于标示测试时 hot observables 的被订阅点。这是 hot observable 的 "zero frame",每个 ^ 之前的 frame 都是负值。

示例表格:

| 时间 (Time) | -   | 0   | 1   | 2   | 3   | 4   | 5   |
|-------------|-----|-----|-----|-----|-----|-----|-----|
| Observable  | -   | a   | b   | c   | -   | d   | \|  |
|             | ' ' | '-' | '-' | '-' | ' ' | '-' | '|' |

17.4)🍀测试工具与示例

import { TestScheduler } from 'rxjs/testing';
import { throttleTime } from 'rxjs';

const testScheduler = new TestScheduler((actual, expected) => {
  expect(actual).deep.equal(expected);
});

it('generates the stream correctly', () => {
  testScheduler.run((helpers) => {
    const { cold, time, expectObservable, expectSubscriptions } = helpers;
    const e1 = cold(' -a--b--c---|');
    const e1subs = '  ^----------!';
    const t = time('   ---|       '); // t = 3
    const expected = '-a-----c---|';

    expectObservable(e1.pipe(throttleTime(t))).toBe(expected);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });
});

TestScheduler 构造器得到一个实例,示例中需要可以进行断言测试,可以用 chai、jest或者vitest等其他断言库提供。

testScheduler 实例,提供了 run 方法,在 run 的函数参数,函数参数包含 helpers 包含测试常用的对象:

  • cold
  • time
  • expectObservable
  • ...

等等api, 此时根据自己的需求使用。

十八、小结

本文旨在加强对 RxJS 基础认知和知识结构的构建,本文试图磨平 RxJS 基础到进阶的难度。基本包含 RxJS 的所有基础内容。本文结合文字,图片,思维导图,短视频视图全方位讲解并熟悉 RxJS, 但想要熟悉 RxJS 任然需要大量的练习。本文是2023年9月开始认真做 RxJS 和实战 RxJS 一次基础的总结,后面随着对源码的理解加深和对使用场景的加深,以及其他能力的加深,出第二个版本。希望能够帮助到大家。