rxjs - BehaviorSubject 向所有订阅者发送相同的状态引用

标签 rxjs aurelia rxjs6

在我们的单页应用程序中,我们开发了一个集中式存储类,该类使用 RxJS 行为主体来处理应用程序的状态及其所有变化。我们应用程序中的几个组件正在订阅我们商店的行为主题,以便接收对当前应用程序状态的任何更新。然后将此状态绑定(bind)到 UI,以便每当状态更改时,UI 都会反射(reflect)这些更改。每当组件想要更改状态的一部分时,我们都会调用由我们的 store 公开的函数,该函数执行所需的工作并更新对行为主体调用 next 的状态。到目前为止没有什么特别的。 (我们使用 Aurelia 作为执行 2 路绑定(bind)的框架)

我们面临的问题是,一旦组件更改了它从商店接收到的本地状态变量,即使没有在 subejct 本身上调用 next(),其他组件也会更新。

我们还尝试订阅主题的 observable 版本,因为 observable 应该向所有订阅者发送不同的数据副本,但看起来情况并非如此。

看起来所有主题订阅者都在接收存储在行为主题中的对象的引用。

import { BehaviorSubject, of } from 'rxjs'; 

const initialState = {
  data: {
    id: 1, 
    description: 'initial'
  }
}

const subject = new BehaviorSubject(initialState);
const observable = subject.asObservable();
let stateFromSubject; //Result after subscription to subject
let stateFromObservable; //Result after subscription to observable

subject.subscribe((val) => {
  console.log(`**Received ${val.data.id} from subject`);
  stateFromSubject = val;
});

observable.subscribe((val) => {
  console.log(`**Received ${val.data.id} from observable`);
  stateFromObservable = val;
});

stateFromSubject.data.id = 2;
// Both stateFromObservable and subject.getValue() now have a id of 2.
// next() wasn't called on the subject but its state got changed anyway

stateFromObservable.data.id = 3;
// Since observable aren't bi-directional I thought this would be a possible solution but same applies and all variable now shows 3

我用上面的代码做了一个stackblitz。
https://stackblitz.com/edit/rxjs-bhkd5n

到目前为止,我们唯一的解决方法是在我们支持版本的某些订阅者中克隆状态,如下所示:

observable.subscribe((val) => {
  stateFromObservable = JSON.parse(JSON.stringify(val));
});

但这感觉更像是一个黑客而不是一个真正的解决方案。一定会有更好的办法...

最佳答案

是的,所有订阅者都会收到行为主体中对象的相同实例,这就是行为主体的工作方式。如果要改变对象,则需要克隆它们。

我使用这个函数来克隆我要绑定(bind)到 Angular 表单的对象

const clone = obj =>
  Array.isArray(obj)
    ? obj.map(item => clone(item))
    : obj instanceof Date
    ? new Date(obj.getTime())
    : obj && typeof obj === 'object'
    ? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
        o[prop] = clone(obj[prop]);
        return o;
      }, {})
    : obj;

因此,如果您有一个 observable data$,您可以创建一个 observable clone$,该 observable 的订阅者可以获得一个可以在不影响其他组件的情况下进行变异的克隆。
clone$ = data$.pipe(map(data => clone(data)));

因此,仅显示数据的组件可以订阅 data$ 以提高效率,而那些会改变数据的组件可以订阅 clone$。

阅读我的 Angular 库 https://github.com/adriandavidbrand/ngx-rxcache和我的文章 https://medium.com/@adrianbrand/angular-state-management-with-rxcache-468a865fc3fb它需要克隆对象,这样我们就不会改变绑定(bind)到表单的数据。

听起来您商店的目标与我的 Angular 状态管理库相同。它可能会给你一些想法。

我不熟悉 Aurelia 或者它是否有管道,但该克隆功能在商店中可用,通过 clone$ observable 公开我的数据,并在模板中使用克隆管道,可以像这样使用
data$ | clone as data

重要的部分是知道何时克隆和不克隆。如果数据将发生变异,您只需要克隆。克隆只显示在网格中的数据数组是非常低效的。

关于rxjs - BehaviorSubject 向所有订阅者发送相同的状态引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56691676/

相关文章:

javascript - 如何过滤 redux store 的 RxJS observables?

javascript - 如何实现关系/嵌套可观察量?

node.js - rxjs 订阅返回重复项

javascript - 当 customElements 加载时从 activate() 返回 Promise

angular - 我需要 `complete()` takeUntil ngOnDestroy 内的主题吗?

javascript - RxJS 异步请求更新

javascript - 在 Aurelia 中强制进行脏检查

javascript - 如何使用 Aurelia 将换行符呈现为 <br> 标记

angular - RXJS6 升级 : ErrorObservable has no exported member 'ErrorObservable'

angular - 如何在 Angular 6 中再次触发 HttpGet 请求?