在 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 应用程序,鉴于其简单性,它不会将服务器响应包装在模型对象中。


问题:

在存储中保留已初始化的类有哪些不利影响(除了可序列化性)?

如果只在商店中保存普通对象,那么在通过应用程序的数据流中,模型classes 的最佳初始化是什么?

【问题讨论】:

选择在状态选择器中初始化模型类并确保所有类在serializefn 处实现,返回普通对象。似乎是最好的选择: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

NgRx 中的 reducer 增强剂等价物是啥?

调度 @ngrx/router-store 操作时出现 Redux devtools 扩展错误

角度:ngrx 效果不触发

NativeScript + ngrx 存储 + 远程开发工具