在 redux / ngrx 应用程序架构中初始化模型对象的位置
Posted
技术标签:
【中文标题】在 redux / ngrx 应用程序架构中初始化模型对象的位置【英文标题】:Where to initialise model objects within a redux / ngrx application architecture 【发布时间】:2017-09-17 03:23:46 【问题描述】:背景:
团队正在使用 Angular 和用于状态管理的 @ngrx 库构建一个基于 REST 的大型 Web 应用程序。
我们希望将来自服务器的实体建模为 TypeScript 类。这些可能是:帐户、用户等
这实现了:
与 API 的松散耦合;如果响应发生变化,只有模型必须改变 封装基本功能,例如名字和姓氏的字符串连接以生成fullName
不确定性在于,在应用程序的时间线内,何时初始化模型,调用:new Account(accountResponse)
。
传统逻辑建议尽早执行此操作,在服务中与检索帐户的逻辑(无论是从缓存、服务器响应等)一起。
this.apiService.fetch(AccountService.URL)
.map(accounts => accounts.map((a: AccountResponse) => new Account(a)));
这个方法被一个ngrx effect调用,然后在一个成功的响应之后,Account对象被reducer添加到store中。
这可行,但是... ngrx / redux "best practice" 声明只有普通对象和原语应保留在存储中,以便于序列化等原因。
要遵守这个建议,必须在更远的地方初始化 Account 对象。无论是在单个组件中,in a state selector,还是通常在使用帐户的任何地方。
这对我来说没有意义,因为原始帐户响应对象正在应用程序中传递,这在某种程度上违背了首先将它们包装在模型中的意义。
该应用程序在结构上类似于@ngrx/example book 应用程序,鉴于其简单性,它不会将服务器响应包装在模型对象中。
问题:
在存储中保留已初始化的类有哪些不利影响(除了可序列化性)?
如果只在商店中保存普通对象,那么在通过应用程序的数据流中,模型class
es 的最佳初始化是什么?
【问题讨论】:
选择在状态选择器中初始化模型类并确保所有类在serialize
fn 处实现,返回普通对象。似乎是最好的选择:1)状态只从选择器中读取,因此我们总是与模型交互,并且:2)每当状态更新时,我从 reducer 调用的序列化 fn
1.您的模型是直接 1-1 映射到服务器响应 json 吗? 2.new Account(response)
构造函数除了将模型属性分配给响应json属性之外,还有什么特别的吗?
一个值得一提的结果是选择器在负责初始化模型时的行为方式。在内部,select
使用 distinctUntilChanged()
运算符。这将执行引用相等检查以不发出未更改的值。然而,使用new
初始化一个新对象,总是会使一个对象看起来不同,而不管包含的数据是否发生了变化。所以你会注意到比预期更多的发射。
@kyranjamie - 我刚开始使用ngrx
。我也在问自己同样的问题。我想知道你最后是怎么解决的。提前非常感谢!
@stevo 我们做出让步,不在数据服务中初始化它们。相反,确保从不在数据服务或存储之外使用或读取纯响应对象。然后,创建一个包装服务以从存储中读取并初始化对象。比如:$users = this.store.pipe(select(selectUsers), map(user => new User(user)))
【参考方案1】:
查看此example,了解如何初始化 ngrx 存储中的功能状态。我希望这就是您正在寻找的。p>
【讨论】:
谢谢,但是这个例子只使用普通对象,所以与这个问题无关。 如果您打算在ngrx中映射实体,您可能需要检查github.com/johnpapa/angular-ngrx-data。 状态也应该是可序列化的。将类添加到状态中使其不可序列化。如果您不打算从服务器序列化/传输状态或将一些数据存储在浏览器 localstorage / indexedDB 或其他地方,那么它有点好。【参考方案2】:使用ngrx 的最简单方法是将其视为一个数据库,而不仅仅是一个javascript 共享对象缓存。您不会将 javascript 助手存储在数据库中,对于 ngrx 也是如此。
您可以停止使用模型函数,而在需要时使用实用程序方法,或者您可以使用可观察运算符包装选择器结果。 state.pipe(select(mySelector), myWrapFunction)
之类的东西,尽管您最终每次都会重新创建包装器。
您可能希望查看视图模型设计模式(例如 [MVVM] (https://en.m.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel))以了解与您尝试做的类似的方法。
【讨论】:
【参考方案3】:@kyranjamie 我正在尝试了解如何以最佳实践方式进行操作。我理解你正确吗?您将普通对象存储在状态中。当组件需要来自状态的对象时,它使用服务,例如: HomeService - 获取 HomeModel 对象而不是普通对象
@Injectable(
providedIn: 'root'
)
export class HomeService
constructor()
public getSelectedHome(homeObject: HomeJSON)
return Object.assign(new HomeModel(), homeObject);
组件需要订阅状态。它订阅了状态的变化,但不是直接使用状态普通对象,而是使用 HomeService 将普通对象变成类对象(HomeModel)。
ngOnInit()
this.store.pipe(select(getSelectedHome)).subscribe(
home =>
this.home = this.homeService.getSelectedHome(home);
);
你是这样做的吗?
【讨论】:
还是直接在选择器中做?例如,在 getSelectedHome 中,我可以使用 HomeService 将普通对象从状态“转换”为类对象(HomeModel)。最好的方法是什么? 不完全是,我们最终遵循外观模式,提供专门用于从商店读取的服务。我对这个问题的最后评论有点解释;我们会从商店中创建一个 observable,就像你上面的select
所做的那样,然后 map
那个流来初始化模型。虽然服务中没有 subscribe
,但我们会使用 async
管道在组件本身中执行此操作。以上是关于在 redux / ngrx 应用程序架构中初始化模型对象的位置的主要内容,如果未能解决你的问题,请参考以下文章
如何在ngrx/effect(redux-observable)中调度多个动作?
Angular NGRX - 为 Redux-Devtools 命名 store/app-instance