在 Angular 服务中频繁使用 BehaviorSubject 是一个危险信号吗?

Cod*_*rer 4 observable rxjs typescript angular

我正在使用 Angular 编写一个应用程序,并发现自己经常使用这种模式:

@Injectable(...)
export class WidgetRegsitryService {
  private readonly _widgets: BehaviorSubject<Widget[]> = new BehaviorSubject([]);
  public get widgets() { return this._widgets.value; }
  public readonly widgets$ = this._widgets.asObservable();

  public add(widget: Widget) {
    const old = this._widgets.value.slice();
    old.push(widget);
    this._widgets.next(old);
  }
}
Run Code Online (Sandbox Code Playgroud)

许多服务将有 3-5 个或更多这样的公共 getter 和私人支持主题组。它发生得太多了,以至于代码感觉非常冗长和重复。所以:a) 有没有一种干的方式来做到这一点,b) 我在这里滥用 Observables 吗?

Rea*_*lar 6

我正在使用 Angular 编写一个应用程序,并发现自己经常使用这种模式:

您展示的模式与状态存储非常相似,例如;ReduxNgRXNGXS。不同之处在于您已将存储、选择器和化简器放在一个类中。

把所有东西放在一个地方有好处,但是如果你每次启动新服务时都必须重写一个新商店,那么这就可以解释为什么你说“它发生的太多以至于代码感觉非常冗长和重复”

这没有什么问题,网上也有很多博文试图用尽可能少的代码行写出一个 Redux 克隆。我的观点是人们一直在做你正在做的事情。

private readonly _widgets: BehaviorSubject<Widget[]> = new BehaviorSubject([]);
Run Code Online (Sandbox Code Playgroud)

上面是状态管理器的存储。它是一个 observable,包含当前状态并发出对该状态的更改。

public get widgets() { return this._widgets.value; }
Run Code Online (Sandbox Code Playgroud)

以上是状态管理器的快照。这允许您在无需订阅的情况下对存储进行特定计算,但就像使用快照的其他状态存储可能存在竞争条件问题一样。您也不应该直接从模板访问它,因为它会触发“检查后表达式已更改”错误。

public readonly widgets$ = this._widgets.asObservable();
Run Code Online (Sandbox Code Playgroud)

以上是商店的选择器。商店通常有许多选择器,允许应用程序的不同部分侦听特定主题的商店更改。

public add(widget: Widget) {
   const old = this._widgets.value.slice();
   old.push(widget);
   this._widgets.next(old);
   // above can be rewritten as
   this._widgets.next([...this.widgets, widget]);
}
Run Code Online (Sandbox Code Playgroud)

我们在州立商店图书馆中没有上述内容。以上分为动作减速器两部分。该操作通常包含有效负载(在您的情况下是一个小部件),并且减速器执行修改存储的工作。

当我们使用 action 和 reducer 时,它将 store 应该如何改变的业务逻辑与读取当前状态、更新状态和保存下一个状态的问题分离。虽然你的例子很简单。在必须订阅、修改和发出更改的大型应用程序中,当您想要做的只是切换布尔标志时,这些更改可能会成为开销样板代码。

许多服务将有 3-5 个或更多这样的公共 getter 和私人支持主题组。它发生得太多了,以至于代码感觉非常冗长和重复。

您正在进入重新发明轮子的领域。

在我看来,您有两种可能的选择。发明你自己的状态存储框架,让你感觉更舒服,或者使用我上面列出的库之一中的现有状态存储。我们无法告诉您该走哪条路,但我参与过许多 Angular 项目,我可以告诉您没有正确的答案。

真正让源代码感觉不那么冗长和重复的东西是高度自以为是的。非常的事情,使得它更简洁也许有一天回来困扰你作为一个设计错误,重复的源代码是痛苦的,但有一天你会感谢你可以修改一行代码,没有它影响其他领域的您源代码。

a) 有没有一种干的方式来做到这一点,以及

干掉源代码的唯一方法是将状态管理的实现与业务逻辑分离。这是我们讨论什么是状态存储的良好设计模式的地方。

  • 你使用选择器吗?
  • 你使用动作吗?
  • 你用减速机吗?

你希望这些东西在哪里(在它们自己的文件中,或服务的方法中?)。你想如何命名它们,你应该重新使用它们还是为每个边缘情况创建新的?

很多问题都是真正的个人选择。

我可以使用NGXS作为示例重写您的示例,但这对您来说可能看起来并不枯燥,因为框架需要复杂才能有用。我可以告诉你的是,当你需要做一些你以前没有做过的事情时,阅读 NGXS 的文档会更容易,然后尝试自己发明它并冒犯错的风险。这并不意味着 NGXS 总是对的,但至少你可以抱怨这不是你的错:)

@State<Widget[]>({
    name: 'widgets',
    defaults: []
})
export class WidgetState {
    @Action(AddWidgetAction)
    public add(ctx: StateContext<Widget[]>, {payload}: AddWidgetAction) {
        ctx.setState([...ctx.getState(), payload]);
    }
}

@Component({...})
export class WidgetsComponent {
    @Select(WidgetState)
    public widgets$: Observable<Widget[]>;

    public constructor(private _store: Store) {};

    public clickAddWidget() {
        this._store.dispatch(new AddWidgetAction(new Widget()));
    }
}
Run Code Online (Sandbox Code Playgroud)

b) 我在这里滥用了 Observables 吗?

绝对不会滥用可观察对象。您已经很好地理解了为什么服务应该是无状态反应性的。我认为您只是自己发现了state store的价值,现在您正在寻找更容易使用它们的方法。