使用 NGXS 管理 Angular 中的状态

随着前端应用程序变得越来越复杂,多个参与者可能以不同的方式影响应用程序的全局状态,因此很容易失去对状态的控制。如果您从未担心过在您的JavaScript应用程序中管理全局状态,那么恭喜您!你是幸运的人之一。对于我们其他人来说,有许多不同的库可用。

CQRS风格的状态管理

如果您不熟悉CQR(命令-查询责任隔离)或Redux,(来自Reaction世界的一个受欢迎的库),下面是管理状态的一些基本原则的简要概述。

1.真相只有一个来源,由商店管理。 2.存储状态是不可变的;它不能直接改变。 3.对状态的更改由reducer进行,reducer函数将当前状态和对状态采取的动作作为参数,并在动作完成操作后返回一个全新的状态对象。

在以前的article](https://andsky.com/tech/tutorials/angular-socket-io),中,我们使用ANGLE和Socket.IO构建了一个非常基本的文档协作应用程序,用于客户端和服务器之间的实时通信。对于集成像NGXS.)这样的状态管理库,这种应用程序是一个完美的用例当您有来自不同参与者的更新在不同时间传入时-在本例中,用户可以进行更新,而服务器可以推送更新-拥有状态容器就很方便了。我将使用我们在上一篇文章中构建的应用程序作为本文中示例的基础。

NGXS角度状态管理

从NPM安装最新的@ngxs/store包开始。

1$ npm i @ngxs/store --save

您将创建一些新的ES2015类来表示状态和操作。这取决于你想如何构造这些文件。为了清楚起见,我将把所有内容都写在一个文件中,可能称为state.ts。实际上,您可能希望将这些类分离到它们自己的文件结构中。

状态

现在,让我们创建两个类型来表示应用程序状态的片段。这些将是我们的州集装箱。您还将添加一些关于状态容器的元数据。

 1import { State } from '@ngxs/store'
 2
 3export interface DocumentStateModel {
 4  id: string;
 5  doc: string;
 6}
 7
 8@State<DocumentStateModel>({
 9  name: 'document', // required
10  defaults: { // optional: DocumentStateModel
11    id: '',
12    doc: 'Select an existing document or create a new one to get started'
13  }
14})
15export class DocumentState { }
16
17@State<string[]>({
18  name: 'documentList',
19  defaults: ['']
20})
21export class DocumentListState { }

我本可以将整个应用程序的状态放在一个@state装饰类中,其中一个属性用于当前文档,一个属性用于文档列表,但我已经创建了两个不同的状态类,DocumentStateDocumentListState,以向您展示您的存储中可以有任意多的状态容器。

状态对象受益于ANGLE的依赖项注入系统,因此如果我们想要将服务注入状态容器,我们可以:

1export class DocumentListState {
2  constructor(private documentService: DocumentService) { }
3}

操作

定义动作允许我们非常明确地说明我们的状态应该做什么,以响应应用程序中发生的用户事件或事情。

下面是我们如何定义一个向状态添加新文档的简单操作:

1export class AddDocument {
2  static readonly type = '[Document List] Add Document'; // required
3}

操作也可以伴随着有效负载。假设我们打开的文档正在被其他人编辑,而我们的套接字服务器正在向下推送更改。我们的组件可能会通过分派如下所示的操作来响应:

1export class DocumentEditedFromServer {
2  static readonly type = '[Document] Edited From Server';
3  constructor(public docText: string) { }
4}

现在回到我们的状态类,我们需要定义当我们的组件将这些操作分派到存储时应该发生什么。

 1export class DocumentState {
 2  @Action(DocumentEditedFromServer)
 3  editDocument(ctx: StateContext<DocumentStateModel>, action: EditDocument) {
 4    const state = ctx.getState(); // always returns the freshest slice of state
 5    ctx.setState({
 6      ...state, 
 7      doc: action.docText
 8    }); // the spread operator is shorthand for Object.assign({}, state, { doc: docText });
 9  }
10}

我们从不直接修改状态;相反,我们创建状态的副本,更改副本的任何属性,并将整个状态对象设置为修改后的副本。

如果需要,可以通过返回一个在其可管道操作符之一中设置状态的可观察对象来异步执行您的操作。一个例子可能是触发API调用并在响应中更新状态的操作。您不需要订阅可观察对象;框架将为您完成这项工作,因此您需要更新pipe链中的状态,例如,使用`ap‘运算符。

选择

有几种方法可以将数据从存储中取出。首先,我们可以在组件中定义选择属性:

 1@Component({ /*...*/ })
 2export class DocumentListComponent {
 3  // Stream of the entire Document List State
 4  @Select(DocumentListState) documents$: Observable<string[]>;
 5
 6  // @Select doesn't need a parameter if the name of the 
 7  // property matches the name of the state you're selecting.    
 8  @Select() documentList$: Observable<string[]>;
 9
10  // You can also use a function to get the slice of state you need.
11  @Select(state => state.documentList): Observable<string[]>;
12}

记忆选择器

如果您有一个特定的函数需要用来选择,您可以重复使用它,您还可以记住静态的选择函数。在您所在的州班级中:

1@State<string[]>( /*...*/ )
2export class DocumentListState {
3  @Selector()
4  static lastTenDocuments(state: string[]) {
5    return state.slice(-10);
6  }
7}

现在,在您的组件中,您可以访问备注选择器。

1export class DocumentListComponent {
2  @Select(DocumentListState.lastTenDocuments) recentDocuments$: Observable<string[]>;
3}

店铺服务选择

您还可以选择将存储像服务一样注入到组件中,并直接从中进行选择。

1export class DocumentComponent {
2  currentDocument$: Observable<DocumentStateModel>;
3  constructor(private store: Store) {
4    this.currentDocument$ = this.store.select(state => state.document);
5  }
6}

在不能使用可观测对象的情况下,您还可以选择状态的静态快照。请注意,只有在您选择快照时,状态才是最新的。一个很好的用例是在Interceptor类中,此时您可能需要数据,而订阅没有意义。

1this.docId = this.store.selectSnapshot<string>(state => state.document.id)

结论

我们仅仅触及了NGXS在状态管理方面所能提供的皮毛。NGXS及其社区还提供了许多其他高级模式、特性、工具和插件,这些超出了本文的范围,但我们希望在以后的文章中探讨这些内容。

进一步阅读

Published At
Categories with 技术
Tagged with
comments powered by Disqus