在 RxJS 中,Subject 既是一个发布者也是一个订阅者,它是一种常用的 Observable 类型。Subject 可以在不同的 Observable 间充当桥梁,将多个 Observable 整合到一个 Observable 中去,同时能够实现数据的共享与传递。本文将会介绍 Subject 类型的基本用法,以及对它的深入剖析,希望能够对你的 RxJS 学习或工作有所帮助。
1. Subject 的基本介绍
Subject 是 RxJS 中的一种特殊类型的 Observable,同时拥有 Observable 和 Observer 的双重身份。从 Observable 的角度看,Subject 可以订阅一个事件流,同时再将这个事件流转化为另外一个事件流并发射出去。从 Observer 的角度看,Subject 可以作为数据源,将外部数据流发送到内部,随后再将内部的数据流发射到外部。因此,Subject 可以实现多个观察者对同一个数据源进行订阅,实现数据的共享和传递。
RxJS 提供了五种不同的 Subject 类型。其中,BehaviorSubject、ReplaySubject、AsyncSubject 是最常用的类型,而且都继承于 Subject 类,因此我们将会对这三种类型进行详细介绍。
1.1. BehaviorSubject
BehaviorSubject 是一个当前值得 Subject,它要么没有初始值,要么会有一个初始值。当一个观察者订阅了 BehaviorSubject,它会立刻收到 Subject 最后一次发射的值(如果不存在,则是初始值)。它可以将这个值缓存下来,随后再发给其他的观察者,保证所有的观察者都能接收到 Subject 最近的变化。
BehaviorSubject 在初始化时需要设置一个初始值。下面是 BehaviorSubject 的一个例子:
-- -------------------- ---- ------- ------ - --------------- - ---- ------- ----- ------- - --- ------------------------- ------------------- ----- ------ ------- -- ----------------------- -- --------- --- ------------------- -------- ------------------- ----- ------ ------- -- ----------------------- -- --------- --- --------------------- --------
在上面的代码中,第一个观察者首先会收到 Hello
这个值(因为这是 BehaviorSubject 的初始值),然后从 subject.next()
中订阅到 Hello World
和 Goodbye World
两个值。第二个观察者同样也是从 Hello World
开始订阅的,然后接收到 Goodbye World
这个值。因此 BehaviorSubject 的特点就是能缓存下最新的值,并能够在有新的观察者订阅时提供当前最新值的通知。
1.2. ReplaySubject
ReplaySubject 类型在创建时需要指定一个参数 n,这个参数表示需要缓存的最近的 n 个数据,当有新的观察者订阅时,缓存中的这些数据将会被推送到新的订阅者那里。如果不传 n 的值,则缓存所有的数据。当然,该类型的缺点就是需要缓存所有的值,占用相应的内存空间。
下面是 ReplaySubject 的一个示例:
-- -------------------- ---- ------- ------ - ------------- - ---- ------- ----- ------- - --- ----------------- ------------------- ----- ------ ------- -- ----------------------- -- --------- --- ---------------------- ---------------------- ------------------------ ------------------- ----- ------ ------- -- ----------------------- -- --------- ---
上面的代码中,在第二个观察者订阅前,ReplaySubject 会缓存下最近的两个数据 World
和 Goodbye
,一旦第二个观察者订阅时,这些数据就会被推送给它。ReplaySubject 的特点在于允许观察者重放离线数据,这在一些延迟加载的场景下是非常好用的。
1.3. AsyncSubject
AsyncSubject 是一个通过调用 complete
来执行发射的 Subject,也就是说只有当 Observable 序列完成之后,AsyncSubject 才会发射最后一个值。如果 Observable 序列因为出错而终止时,则 AsyncSubject 不会发射任何值。由于 AsyncSubject 只关注 Observable 序列最后一个值,因此只有在 complete 执行时才会发生。
下面是 AsyncSubject 的一个示例:
-- -------------------- ---- ------- ------ - ------------ - ---- ------- ----- ------- - --- --------------- ------------------- ----- ------ ------- -- ----------------------- -- --------- --- ---------------------- ---------------------- ------------------- ----- ------ ------- -- ----------------------- -- --------- --- ------------------------ -------------------
在上面的例子中,AsyncSubject 并不会发出任何值,直到 complete 方法被调用。在 complete 调用之后,AsyncSubject 会将最后一次调用 next 方法发出的值 Goodbye
推送到所有的观察者那里。AsyncSubject 可以实现无论序列是否有多个值,它只返回最新一个值的聚合场景。
2. Subject 的使用技巧
通过对 Subject 的基本介绍,我们知道了 Subject 是什么?它有哪些类型,它们各自的特点以及使用场景。但是单纯的了解它们仍然无法完全掌握 Subject,只有深入掌握它们的使用技巧才能真正得到它们的效果。
2.1. Subject 与其他 Observable 的转换
Subject 实例可以通过调用 asObservable()
方法,将 Subject 实例转换成一个 Observable 实例,以此实现对 Subject 的保护。这个方法非常地有用,它可以让 Subject 变成一个只有只读属性的 Observable,使得 Subject 的一些操作无法得到观察者的响应。
例如:
-- -------------------- ---- ------- ------ - ------- - ---- ------- ----- ------- - --- ---------- ----- ---------- - ----------------------- ---------------------- ----- ------ ------- -- ----------------------- -- --------- --- ------------------- -------- ---------------------- ----- ------ ------- -- ----------------------- -- --------- --- --------------------- --------
在上面的代码中,首先我们把 subject 转换成了一个只读的 observable,然后通过这个只读的 observable 订阅了两个观察者。接着我们从 subject 中发送了两个消息,可以看到只有第一个观察者收到了两个消息,而第二个观察者只收到了第二个消息。这是因为第二个观察者订阅 observable 时,只读属性已经对 subject 进行了保护,从而无法实时地收到第一个消息。
2.2. 操作符的转换
在使用 Subject 时,除了上面所提及的 asObservable()
方法,还可以使用 RxJS 的操作符对 Subject 进行转换或者流转。下面是一些常用的操作符:
- multicast 和 refCount:可以将结果转换为 ConnectableObservable,使得一个 Subject 能够同时让多个订阅者订阅而不产生多个订阅。
- publish 和 refCount:用一种简单的方式实现 multicast 和 refCount 的功能。
- share 和 publishReplay:用于自动处理自动重放的配置,自动管理缓存,以提高性能和减少网络流量。
- publishBehavior:用一个 Behavior Subject 实现的 publish 操作符。
- publishLast:用一个 AsyncSubject 实现的 publish 操作符。
下面是 publish 和 refCount 操作符的一些例子:
-- -------------------- ---- ------- ------ - -------- -- - ---- ------- ------ - ---------- --------- --- - ---- ----------------- ----- ------- - ----------- --------- ----- ------- - --- ---------- -- - ------- --- --------- ---------- -- --- -------- --------- ----- ---------- - ------------- ---------- -- ---------------- ----- ----------- ------------------- ---------- -- --------------------------- -- ----------------------- -- --------- -- --------------------------- -- ----------------------- -- --------- -- ---------------------- ----- ------ -- ----------------------- -- ---------- --------- -- -- ----------------------- - ------------ --- -- ---- --------------------- -- ---- ----
上面的示例里,我们使用 multicast 和 refCount 操作符将 Subject 转换成了一个 ConnectableObservable,它使得多个观察者可以同时订阅一个源,而把它们转化成一个共享的流,提高了性能,并防止订阅多次。由此我们可以知道,当我们需要连接到一个可以连接的 Observable 时,这些操作符可以对我们非常有帮助。
2.3. Subject 的误用
然而,我们在使用 Subject 时也有可能会发生一些错误,这些错误需要我们尤其注意。下面是一些在使用 Subject 时需要避免的误区:
- 将 Subject 作为装饰者或者代理使用。Subject 的主要作用是传递值,它不应该处理共享状态或者输入的更新。
- 将 Subject 用于从上下文中传递数据,例如在组件之间传递数据。建议使用 Angular 的
@Input()
和@Output()
装饰器。 - 对于没有缓存的 Subject(例如 Subject 和 AsyncSubject),应该确保在订阅时,发射所有必须的值,否则将会丢失这些值。
- 避免在订阅时直接调用
complete()
,因为它会错误地完成流并将其打乱。 - 避免串联同类 Subject,例如在一个 Subject 中订阅另一个 Subject,这可能会导致性能问题和内存泄漏。
结论
本文介绍了 RxJS 中 Subject 的基本介绍和使用技巧。通过本文,你应该能够知道什么是 Subject,它有哪些类型及各自的特点,在程序中如何使用 Subject 进行数据的共享和传递。同时,你还应该注意到在使用 Subject 时会发生的一些错误,需要注意避免这些错误。希望本文能够对你的 RxJS 学习和工作有所帮助。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/674e38ba947dc5bcb308f9bb