Angular 中的 Subject vs BehaviorSubject vs ReplaySubject

Posted

技术标签:

【中文标题】Angular 中的 Subject vs BehaviorSubject vs ReplaySubject【英文标题】:Subject vs BehaviorSubject vs ReplaySubject in Angular 【发布时间】:2017-08-24 10:22:10 【问题描述】:

我一直在寻找了解这 3 个:

Subject BehaviorSubject ReplaySubject

我想使用它们并知道何时以及为什么使用它们,使用它们有什么好处,尽管我已经阅读了文档、观看了教程并搜索了谷歌,但我无法理解这一点。

那么他们的目的是什么?一个真实的案例将不胜感激,它甚至不需要编码。

我希望有一个清晰的解释,而不仅仅是“a+b => c 你订阅了....”

谢谢

【问题讨论】:

已经有一个带有 observable 的行为主题的问题; ***.com/questions/39494058/… 和有关重播主题的文档很清楚 imo github.com/Reactive-Extensions/RxJS/blob/master/doc/api/… answer 中对 Rxjs 中的主题进行了相对全面的介绍,很好地补充了 peeksilet 的答案。这还包括有关终止后行为的重要细节,所以看看就好。 【参考方案1】:

这真的归结为行为和语义。用一个

Subject - 订阅者只会获得在订阅之后发出的发布值。问问自己,这是你想要的吗?订阅者是否需要了解有关先前值的任何信息?如果没有,那么您可以使用它,否则选择其他之一。例如,组件到组件的通信。假设您有一个组件在单击按钮时发布其他组件的事件。您可以使用带有主题的服务进行通信。

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

BehaviorSubject 被初始化为一个初始值。这有时对编码偏好很重要。例如,您使用null 对其进行初始化。然后在您的订阅中,您需要进行空检查。可能没问题,也可能很烦人。

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

如果您使用1 的缓冲区大小初始化ReplaySubject,那么它实际上表现就像BehaviorSubject。最后一个值总是被缓存,所以它就像一个随时间变化的值。有了这个,就不需要像使用null 初始化的BehaviorSubject 那样进行null 检查。在这种情况下,在第一次发布之前,不会向订阅者发送任何值。

所以这真的归结为您所期望的行为(至于使用哪一个)。大多数时候,您可能想要使用BehaviorSubject,因为您真正想要表示的是“随时间变化的价值”语义。但我个人认为用1 初始化的ReplaySubject 的替换没有任何问题。

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

另见:

What are RxJS Subject's and the benefits of using them?

【讨论】:

这是简短易懂的区别。当服务中的值发生变化并且组件也发生变化以显示该值时,BehaviourSubjects 或 Replay Subject 是解决方案。 谢谢! ReplaySubject 缓冲区大小为 1 正是我所需要的。我有一个需要该值的路由守卫,但需要等待第一次发射。所以BehaviorSubject 并没有削减它,因为我不想要一个初始值(null 也不起作用,因为我用它来表示一个状态) ReplaySubject 缓冲区为 1 与 BehaviorSubject 不同,ReplaySubject 将阻止订阅者等待第一个值,而 BehaviorSubject 在创建时需要初始值。通常,您希望根据需要延迟获取数据,并且没有任何初始值。 "如果你初始化一个缓冲区大小为 1 的ReplaySubject,那么它的行为实际上就像一个BehaviorSubject":这并不完全正确;检查this great blog post 这两者之间的差异。例如,如果您订阅已完成的 BehaviorSubject,您将不会收到最后一个值,但对于 ReplaySubject(1),您将收到最后一个值。 我认为您可以为重播主题提及的一个非常简单的示例是“聊天室”或游戏大厅场景,您希望新加入者看到最后 10 条消息。【参考方案2】:

对不同可观察类型的方便总结,我知道的非直观命名哈哈

Subject - 订阅者只会在订阅后获得发布的值。 BehaviorSubject - 新订阅者在订阅后立即获得最后发布的值或初始值。 ReplaySubject - 新订阅者在订阅后立即获得所有以前发布的值

【讨论】:

1-n 个已发布的值?因此,如果有 2 个已发布的值,ReplaySubject 将产生 -1 个已发布的值??? @JasonCheng 不,它会在订阅时检索所有以前发布的值,更新答案:)【参考方案3】:
    主题:在订阅时,它总是获取订阅后推送的数据,即没有收到之前推送的值

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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.12/Rx.min.js"></script>

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

From subscription 1: 2  
From subscription 1: 3  
From subscription 2: 3  
From subscription 2: 4  

请注意迟到的订阅是如何丢失一些已推送到主题中的数据的。

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

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

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);
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.12/Rx.min.js"></script>

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

From 1st sub: 3
From 1st sub: 4
From 1st sub: 5
From 2nd sub: 4
From 2nd sub: 5
    行为主题:类似于重放主题,但只会重新发出最后发出的值,如果之前没有发出过值,则使用默认值:

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);
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.12/Rx.min.js"></script>

结果:

From 1st sub: Hey now!
From 1st sub: 5
From 2nd sub: 5

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

【讨论】:

参考文章真的很有帮助【参考方案4】:

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

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


这并不完全正确;检查this great blog post 这两者之间的差异。例如,如果您订阅了一个完整的 BehaviorSubject,您将不会收到最后一个值,但对于 ReplaySubject(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));

查看此代码示例here 来自another great blog post 的主题。

【讨论】:

【参考方案5】:

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

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

我们可以使用 ReplaySubject 来跟踪历史记录。 ReplaySubject 记录最近的 n 个事件并将它们回放给每个新订阅者。例如在聊天应用程序中。我们可以用它来跟踪以前的聊天记录。

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

【讨论】:

【参考方案6】:

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

在 cmets 中经常提到“守卫”,这也是我最常发现 Replay 主题用例的地方。更具体地说,如果您有类似 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';
      
    );
  

结果:


  "behaviorSubject": "routed to landing page",
  "replaySubject": "routed to dashboard"

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

【讨论】:

【参考方案7】:
     // ***********Subject  concept ***********
    let subject = new Subject<string>();


    subject.next("Eureka");
    subject.subscribe((data) => 
      console.log("Subscriber 1 got data >>>>> "+ data);
    );
    subject.subscribe((data) => 
      console.log("Subscriber 2 got data >>>>> "+ data);
    );

       // ********behaviour subject*********
    // Behavior subjects need a first value
let subject1 = new BehaviorSubject<string>("First value");


subject1.asObservable().subscribe((data) => 
  console.log("First subscriber got data behaviour subject>>>>> "+ data);
);
subject1.next("Second value")
主题 - 订阅者只有在订阅后才能获得发布的值。 BehaviorSubject - 新订阅者在订阅后立即获得最后发布的值或初始值。

【讨论】:

以上是关于Angular 中的 Subject vs BehaviorSubject vs ReplaySubject的主要内容,如果未能解决你的问题,请参考以下文章

o.Subject 不是构造函数 - Angular 10

关于rxjs subject订阅分发实现Angular的全局数据管理与同步更新

Angular和Rxjs:写入单元测试以过滤Subject

观察者模式Vs发布订阅模式

Angular - VS2015中的类型错误不存在属性'$inject'

找不到模块 'rxjs/subject/BehaviorSubject'