在Angular 2中使用Store(ngrx)有哪些好处?

64

我正在开发一个基于angular 1.x.x的项目,并考虑将我的代码升级到angular 2

现在,在我的项目中,我有许多服务(工厂)用于处理数据,这些服务几乎都将数据存储在js数组中(包括缓存和存储),并使用underscore来处理这些数据。

我发现许多angular2的示例都使用ngrx。

与使用数据服务处理数据相比,使用store的好处是什么?

如果我有多个数据类型(股票、订单、客户等),是否需要为我的应用程序使用多个store?

如何设计我的应用程序以处理这些不同的数据类型?


1
似乎以下回答都没有涉及到存储服务相对于数据服务的好处。起初,有多个Flux存储器,基本上就是您以前用来存储数据的服务,提取了一些常见的样板逻辑并以某种方式进行了重构。下一个演进步骤Redux将所有这些多个存储器合并为一个。存储器减速器、效果等仍然包含您在服务中保留的相同逻辑,只是被重新排列并包装在一个存储器中,为您提供方便的方法来访问您的数据。 - Dima Slivin
1
正如@LordTribual的回答所示,我认为最重要的好处是它将您紧密地绑定到一种模式,确保数据仅通过操作进行更改,并且操作在系统中是清晰/明确的。这在数据服务中不能得到保证(或不需要)。在具有多个共享数据的系统中,这种模式将非常有帮助。 - Vu Quyet
3个回答

84
尽管您的问题主要是基于个人意见的,但我可以给您一些关于为什么ngrx是一个不错选择的想法。尽管有人说将所有应用程序状态放在一个单一对象(Single State Tree)中并不是一个好主意,但是,在我看来,您的状态无论如何都会存在。使用存储区,它只是在一个地方,并且变化是明确和可跟踪的,而不是在各处散布并由组件本地维护。此外,您可以在应用程序内从存储库中选择特定属性,因此只能选择您关心的数据。如果您在减少器中采用不可变性,例如始终返回数组并使用Observables,则可以利用ChangeDetectionStrategy OnPush。OnPush可以为您提供良好的性能提升。让我们来看看下面这张图,它取自官方Angular docs

enter image description here

如您所见,Angular应用程序是使用组件架构构建的,这导致了一个组件树。在组件上使用OnPush意味着仅当输入属性更改时,才会启动变更检测。例如,如果Child BOnPush,而Child ADefault,并且您在Child A内部更改了一些内容,则不会触发Child B的变更检测器,因为没有更改输入属性。但是,如果您在Child B内部更改了某些内容,则Child A将重新呈现,因为它具有默认变更检测器。

关于性能和单状态树就讲到这里。存储的另一个优点是,您实际上可以推理出代码和状态更改。因此,大多数Angular 1.x应用程序的现实是“作用域汤”。以下是Lukas Ruebbelke的博客文章中的一张漂亮的图形:

enter image description here

这张图片很好地演示了它。Tero Parviainen的另一篇文章谈到了如何通过禁用ng-controller来改善他的Angular应用程序。这都涉及到作用域混乱和管理不断变化的状态是困难的。 redux的动机在这里说得很清楚:

如果一个模型可以更新另一个模型,那么视图就可以更新一个模型,进而更新另一个模型,这反过来可能会导致另一个视图更新。在某个点上,您不再理解应用程序中发生的事情,因为您已经失去了对其状态何时、为什么以及如何控制的控制权。当系统不透明且不确定时,很难重现错误或添加新功能。

通过使用ngrx/store,您实际上可以解决此问题,因为您将获得应用程序中清晰的数据流。

由于ngrx受redux的高度启发,我认为相同的主要原则适用:

  • 单一数据源
  • 状态只读
  • 使用纯函数进行更改

因此,我认为最大的好处是您可以轻松跟踪用户交互并思考状态更改,因为您会分派操作,这些操作始终导致一个位置,而对于普通模型,您必须找到所有引用并查看何时更改了什么。

使用ngrx / store还使您能够使用devtools查看调试状态容器并恢复更改。时间旅行,我想,是redux的主要原因之一,如果您使用普通旧模型,则相当困难。

如@muetzerich已经提到的那样,可测试性也是使用ngrx / store的好处。减速器是纯函数,这些函数易于测试,因为它们接受输入并简单地返回输出,并且不依赖于函数外部的属性并且没有副作用,例如http调用等。

To jump to the bottom line, I would say that you don't need to use ngrx/store to accomplish any of these tasks, but by doing so, you will be bound by constraints (the three principles mentioned above) that provide a common pattern and deliver significant benefits.
To answer your questions:
Do I need multiple stores for my app if I have multiple data types (stock, order, customer...)?
No, I would not recommend using multiple stores.
How can I structure (design) my app to deal with multiple data types like these?
Perhaps this blog post by Tero Parviainen might help you figure out how to design your store. He discusses how to design the application state tree for an example app.

18
但是Angular 2+具有服务。您可以将它们用作唯一的真相来源,并将它们注入所有需要数据的组件中。Redux在React中很受欢迎,因为React缺少许多东西,例如“多余的props”问题。本文讨论了ngrx或其他存储对有限的Angular应用程序的有限好处:https://blog.angular-university.io/angular-2-redux-ngrx-rxjs/。 - Marcel
1
NgRx带来了一些权衡。它通过大量不必要的样板代码呈指数级增加了代码复杂性。另一方面,除了Angular已经默认提供的功能外,它几乎没有提供任何其他东西。本博客文章涵盖了您需要决定是否需要它的所有信息:Angular应用程序状态管理:您(不)需要外部数据存储 - seidme

9
您可以在文档中找到使用 Store 的好处的详细解释。 中央化、不可变状态 所有相关的应用程序状态都存在于一个位置。这使得跟踪问题变得更容易,因为在错误发生时状态的快照可以提供重要的见解,使得重新创建问题变得容易。在 Store 应用程序的上下文中,这也使得像撤销/重做这样的难题变得微不足道,并启用了强大的工具。 性能 由于状态集中在应用程序的顶部,数据更新可以通过依赖 store 片段的组件向下流动。Angular 2 是基于这种数据流排列进行优化的,并且可以在组件依赖其未发出新值的 Observables 的情况下禁用更改检测。在最佳存储解决方案中,这将是您大多数组件的情况。 可测试性 所有状态更新都在 reducers 中处理,这些 reducers 是纯函数。纯函数非常容易测试,因为它仅仅是输入和输出的比较。这使得您可以在没有模拟器、间谍或其他可能使测试变得复杂和容易出错的技巧的情况下对应用程序的最关键方面进行测试。 多个 stores? 在我看来,使用一个 store 并将数据类型作为 store 的属性添加即可。

这是一般的好处。我想比较一下旧的方式(使用数据服务和存储数据类型的数组)。如果只使用一个存储,我可以得到一个大类来处理应用程序中的所有操作。这不是一个好主意吗?我该如何在类型之间分离代码? - Vu Quyet
据我理解,您需要为每种数据类型编写reducer,并使用provideStore将其添加到store中,然后在组件中使用store.select(myreducer)来使用它。 - Derek Kite

7
ngrx.store可以像设计良好的组件/服务一样工作,并具有附加的好处。到目前为止,在我开始研究如何将它们结合起来之前,这就是我发现的:
使用服务和组件很容易变得难以维护。如果您有级联操作、复杂数据交互和对远程位置的调用,那么您最终会构建一个几乎与ngrx中的action-reducer-store安排完全相同的服务。小型纯函数、可观察对象等在ngrx中已经实现了,为什么不使用它并从其代表的思想和模式中获益呢。
它强制/鼓励考虑小型可测试函数。为了布置适用于中等复杂组件的reducer或多个reducer会强制执行一种纪律,这种纪律将消除许多耗费时间的陷阱。没有什么能像跟踪源自回调队列的准多线程竞态条件一样耗费时间。我确定这也可能发生在reducers上,但它简化了调试过程中的调用序列和状态访问。
Angular2模式变得更加容易。带有显示逻辑的模板、作为收集所有组件的位和碎片的组件。服务变得更简单,因为它们只需处理从任何地方获取数据的远程调用或io。然后是用于维护和更改状态的actions和reducers,这将触发所有其他部分响应新状态。我发现使用组件/服务模式时,其中之一会变得又大又复杂,副作用是它变得极难调试。我的服务最终存储状态并执行数据io。
可观察对象。在rxjs.store中,所有内容都是可观察的,这是响应式应用程序的基础。将应用程序状态构建成可观察对象有时可能有点晦涩或不太直观,但是找出来并做好它必将在以后带来巨大的回报。
唯一的缺点是reducers非常快地变得非常大。似乎有很多情况下相同的代码重复出现,重复出现。我的大脑大喊“参数化函数”,但是它不起作用。另一方面,这就是应用程序的结构和状态以所有细节表达的地方,因此肯定会有很多东西。当某些东西出了问题时,作为问题源的纯函数使其更容易跟踪和解决。

服务存储状态并执行数据输入输出的缺点是什么? - Marcel
如果做得好,就没有问题。例如,将Angular的v4更新到v5需要更新为HttpClient。如果服务做得好,只需更改一个文件中的标题代码和响应数据结构,就像我的ngrx设置一样简单。在状态管理系统中添加自动保存、撤销/重做或离线操作等功能非常容易,并且如果您的服务按照相似的方式进行结构化,这些操作也会变得轻松简单。 - Derek Kite

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接