Angular中的Subject vs BehaviorSubject vs ReplaySubject

89 javascript reactive-programming rxjs angular2-observables angular

我一直在寻找那些3:

主题,行为主体重播主题.我想使用它们,知道何时以及为什么,使用它们有什么好处,虽然我已阅读文档,观看教程和搜索谷歌,但我没有对此有任何意义.

那他们的目的是什么?一个真实的案例将是非常感谢它甚至不必编码.

我更喜欢一个干净的解释,而不只是"a + b => c,你订阅了......"

谢谢

Pau*_*tha 204

它真正归结为行为和语义.有了

  • Subject- 订阅者只能获得订阅发布的已发布值.问问自己,这就是你想要的吗?订户是否需要了解以前的值?如果没有,那么你可以使用它,否则选择其中一个.例如,使用组件到组件的通信.假设您有一个组件,可以通过单击按钮发布其他组件的事件.您可以使用与主题进行通信的服务.

  • BehaviorSubject - 缓存最后一个值.订阅者将在初始订阅时获得最新价值.此主题的语义是表示随时间变化的值.例如,登录用户.初始用户可能是匿名用户.但是一旦用户登录,则新值是经过身份验证的用户状态.

    BehaviorSubject被初始化为一个初始值.这有时对编码偏好很重要.比如说你用a初始化它null.然后在您的订阅中,您需要进行空检查.也许好,或者可能很烦人.

  • ReplaySubject - 它可以缓存到指定数量的排放.任何订阅者都将在订阅时获得所有缓存的值.你什么时候需要这种行为?老实说,我不需要这样的行为,除了以下情况:

    如果初始化ReplaySubject用的缓冲区大小1,则其实际行为就像一个BehaviorSubject.最后一个值始终被缓存,因此它的作用就像一个随时间变化的值.通过这种方式,不需要nullBehaviorSubject使用a初始化的情况那样进行检查null,因为在第一次发布之前,没有任何值发送给订户.

所以它确实降低了你期望的行为,关于哪一个使用.大多数时候你可能想要使用a,BehaviorSubject因为你真正想要表达的是"随时间变化的价值"语义.但我个人认为用ReplaySubjectinitialized 替换没有任何问题1.

当你真正需要的是一些缓存行为时,你想要避免使用vanilla Subject.例如,您正在编写路由警卫或解决方案.您在该防护中获取一些数据并将其设置为服务Subject.然后在路由组件中,您订阅服务主题以尝试获取在防护中发出的值.哎呀.价值在哪里?它已经被释放了,DUH.使用"缓存"主题!

也可以看看:

  • *“如果你初始化一个缓冲区大小为 1 的 `ReplaySubject`,那么它的行为实际上就像一个 `BehaviorSubject`”*:这并不完全正确;检查[这篇很棒的博客文章](https://medium.com/javascript-everyday/behaviorsubject-vs-replaysubject-1-beware-of-edge-cases-b361153d9ccf)了解两者之间的差异。例如,如果您订阅已完成的“BehaviorSubject”,您将不会收到最后一个值,但对于“ReplaySubject(1)”,您将收到最后一个值。 (9认同)
  • 这是简短且易于理解的差异。当服务中的值发生变化并且组件也更改了值的显示时,BehaviourSubjects 或 Replay subject 就是解决方案。 (3认同)
  • 谢谢你!缓冲区大小为 1 的“ReplaySubject”正是我所需要的。我有一个路线守卫需要该值,但需要等待第一次发射。所以 `BehaviorSubject` 没有剪切它,因为我不需要初始值(`null` 也不起作用,因为我用它来表示状态) (3认同)
  • 我认为您可以为重播主题提及的一个非常简单的示例是“聊天室”或游戏大厅场景,您希望新加入者看到最后 10 条消息。 (3认同)
  • 缓冲区为 1 的 ReplaySubject 与 BehaviorSubject 的不同之处在于 ReplaySubject 将阻止订阅者等待第一个值,而 BehaviorSubject 在创建时需要一个初始值。通常,您希望按需延迟获取数据并且没有任何初始值。 (2认同)

Var*_*eja 21

  1. 主题:订阅时,它总是获取订阅后推送的数据,即未收到之前推送的值
const mySubject = new Rx.Subject();

mySubject.next(1);

const subscription1 = mySubject.subscribe(x => {
  console.log('From subscription 1:', x);
});

mySubject.next(2);

const subscription2 = mySubject.subscribe(x => {
  console.log('From subscription 2:', x);
});

mySubject.next(3);

subscription1.unsubscribe();

mySubject.next(4);
Run Code Online (Sandbox Code Playgroud)

在这个例子中,这是将在控制台中打印的结果:

From subscription 1: 2
From subscription 1: 3
From subscription 2: 3
From subscription 2: 4
Run Code Online (Sandbox Code Playgroud)

请注意迟到的订阅如何错过推送到主题中的一些数据。

  1. 重播主题:可以通过保留将发送给新订阅的先前值缓冲区来提供帮助。

这是重播主题的使用示例,其中buffer of 2 previous values在新订阅中保留和发出a :

const mySubject = new Rx.ReplaySubject(2);

mySubject.next(1);
mySubject.next(2);
mySubject.next(3);
mySubject.next(4);

mySubject.subscribe(x => {
  console.log('From 1st sub:', x);
});

mySubject.next(5);

mySubject.subscribe(x => {
  console.log('From 2nd sub:', x);
});
Run Code Online (Sandbox Code Playgroud)

这是在控制台上为我们提供的内容:

From 1st sub: 3
From 1st sub: 4
From 1st sub: 5
From 2nd sub: 4
From 2nd sub: 5
Run Code Online (Sandbox Code Playgroud)
  1. Behavior subject:类似于重播主题,但只会重新发出最后发出的值,如果之前没有发出任何值,则为默认值:
const mySubject = new Rx.BehaviorSubject('Hey now!');

mySubject.subscribe(x => {
  console.log('From 1st sub:', x);
});

mySubject.next(5);

mySubject.subscribe(x => {
  console.log('From 2nd sub:', x);
});
Run Code Online (Sandbox Code Playgroud)

结果:

From 1st sub: Hey now!
From 1st sub: 5
From 2nd sub: 5
Run Code Online (Sandbox Code Playgroud)

参考:https : //alligator.io/rxjs/subjects/


Wil*_*ilt 13

大多数赞成的答案显然是错误的,声称:

“如果你ReplaySubject用 1 的缓冲区大小初始化 a ,那么它实际上就像一个BehaviorSubject


这并不完全正确。查看这篇很棒的博客文章,了解两者之间的差异。例如,如果您订阅了 Completed BehaviorSubject,您将不会收到最后一个值,但对于 aReplaySubject(1)您将收到最后一个值。

这是一个不容忽视的重要区别:

const behavior = new BehaviorSubject(null);
const replay = new ReplaySubject(1);

behavior.skip(1).subscribe(v => console.log('BehaviorSubject:', v));
replay.subscribe(v => console.log('ReplaySubject:', v));

behavior.next(1);
behavior.next(2);
behavior.complete();
behavior.subscribe(v => console.log('Late B subscriber:', v));

replay.next(1);
replay.next(2);
replay.complete();
replay.subscribe(v => console.log('Late R subscriber:', v));
Run Code Online (Sandbox Code Playgroud)

在此处查看此代码示例该示例来自另一篇关于该主题的精彩博客文章


H S*_*ogr 8

来自:Randall Koutnik 的书“使用 RxJS 构建反应式网站”。:

一个主题是一个对象,是一个涡轮增压观察到。就其核心而言,Subject 的行为很像一个常规的 observable,但每个订阅都连接到同一个源。主体也是观察者,并且有 next、error 和 done 方法可以一次向所有订阅者发送数据。因为主体是观察者,所以它们可以直接传递到 subscribe 调用中,来自原始 observable 的所有事件都将通过主体发送给其订阅者。

我们可以使用ReplaySubject来跟踪历史。一个ReplaySubject记录最后n个事件和palys他们回到每一个新用户。例如在聊天应用程序中。我们可以用它来跟踪以前的聊天记录。

BehaviorSubject是的简化版本ReplaySubject。该ReplaySubject存储的事件的任意数,BehaviorSubject只记录最新事件的价值。每当BehaviorSubject记录新订阅时,它都会向订阅者发出最新值以及传入的任何新值。BehaviorSubject在处理单个状态单元(例如配置选项)时很有用。


ber*_*ing 8

正如一些帖子中提到的,接受的答案是错误的,因为BehaviorSubject != ReplaySubject(1)这不仅仅是编码风格的偏好。

在评论中经常提到“守卫”,这也是我最常找到重播主题用例的地方。更具体地说,如果您有take(1)类似的场景并且您不仅仅想获取初始值。

检查以下示例:

  ngOnInit() {
    const behaviorSubject = new BehaviorSubject<boolean>(null);
    const replaySubject = new ReplaySubject<boolean>(1);
    this.checkLoggedIn(behaviorSubject, 'behaviorSubject');
    this.checkLoggedIn(replaySubject, 'replaySubject');
    behaviorSubject.next(true);
    replaySubject.next(true);
  }

  checkLoggedIn($userLoggedIn: Observable<boolean>, id: string) {
    $userLoggedIn.pipe(take(1)).subscribe(isLoggedIn => {
      if (isLoggedIn) {
        this.result[id] = 'routed to dashboard';
      } else {
        this.result[id] = 'routed to landing page';
      }
    });
  }
Run Code Online (Sandbox Code Playgroud)

结果:

{
  "behaviorSubject": "routed to landing page",
  "replaySubject": "routed to dashboard"
}
Run Code Online (Sandbox Code Playgroud)

在这些情况下,显然你会想要一个ReplaySubject!工作代码:https://stackblitz.com/edit/replaysubject-vs-behaviorsubject ?file=src%2Fapp%2Fapp.component.ts


Ric*_*yce 7

我知道各种可观察类型的简要概述,不直观的命名,哈哈

  • Subject -订阅后,订阅者将仅获得发布的值。
  • BehaviorSubject -新订阅者在订阅后立即获得最后发布的值或初始值。
  • ReplaySubject-新订阅者1-n在订阅后立即获得最新发布的值(仅当先前发出时)。