网站搜索

typescript - 我如何重构/抽象 ngrx actions,以减少拼写错误的可能性?


更新:看起来 typescript-fsa 可能是我正在寻找的有明确意见的库 https://github.com/aikoven/typescript-fsa/issues/40。任何关于实现细节的帮助都会非常棒!

我觉得当我在做 ngrx 时,我总是在不断地复制和粘贴文件,然后只是重命名部分内容。这很容易出现我典型的手滑错误。虽然我目前没有对我的项目进行测试,但如果一个项目要求 100% 的代码覆盖率,这可能会是一个更加繁琐的任务。

一个微不足道的动作示例,称为计数器:

import { Action } from '@ngrx/store';

export const INCREMENT  = '[Counter] Increment';
export const DECREMENT  = '[Counter] Decrement';
export const RESET      = '[Counter] Reset';

export class Increment implements Action {
  readonly type = INCREMENT;
}

export class Decrement implements Action {
  readonly type = DECREMENT;
}

export class Reset implements Action {
  readonly type = RESET;

  constructor(public payload: number) {}
}

export type All
  = Increment
  | Decrement
  | Reset;

问题:

在 TypeScript 中,有没有什么模式可以通过一个简单的函数调用来实现上面显示的这组类的功能?

一个例子是:

TypeAction.create('Animal', ['添加', '移除', '重置'])

注意:这是一个人为构造的函数签名,因为它没有考虑有效负载的类型,只是作为示例?

但是这个函数可以像下面的代码那样生成/表示/工作:

import { Action } from '@ngrx/store';

export const ADD = '[Animal] Add';
export const REMOVE = '[Animal] Remove';
export const RESET      = '[Animal] Reset';

export class Add implements Action {
  readonly type = ADD;
}

export class Remove implements Action {
  readonly type = REMOVE;
}

export class Reset implements Action {
  readonly type = RESET;

  constructor() {}
}

export type All
  = Add
  | Remove
  | Reset

最后,请不要反思,因为这不是一个好的解决方案。另外,“这是不可能的”也可以是一个可接受的答案……

答案#1

另一个解决方案是使用像 typescript-fsa* 这样的动作生成器

所以,你可以定义动作生成器,而不是“原生”的 NgRx 动作类,例如。

import { actionCreatorFactory } from 'typescript-fsa';

const counter = actionCreatorFactory('counter');
export const counterIncrement = counter('INCREMENT');
export const counterDecrement = counter('DECREMENT');
export const counterReset = counter<number>('RESET');

生成的动作将被命名为 'counter/INCREMENT' 等。

像这样在你的 reducers 中使用它们:

import { isType } from 'typescript-fsa';

export function counterReducer(state: CounterState = initialState, action: Action) {
  if (isType(action, counterIncrement)) {
    return { ...state, < your reduction here > };
  }
  if (isType(action, counterDecrement)) {
    return { ...state, < your reduction here > };
  }
  if (isType(action, counterReset)) {
    return { ...state, < your reduction here > };
  }
  return state;
};

并将这些派发为

store.dispatch(counterIncrement());
store.dispatch(counterDecrement());
store.dispatch(counterReset(42));

最后,在你的影响中

@Effect() counterIncrement$ = this.actions$
  .filter(counterIncrement.match)
  .map(action => ...)

@Effect() counterDecrement$ = this.actions$
  .filter(counterDecrement.match)
  .map(action => ...)

@Effect() counterReset$ = this.actions$
  .filter(counterReset.match)
  .map(action => action.payload)
  .map(payload => ...)

正如你所看到的,除了最初的定义之外,你从不使用容易出错的操作字符串,而是导入类型化的操作生成器,这提供了额外的安全性,并且还使在支持 TypeScript 的代码编辑器中替换函数成为可能。

如果你的操作在各个集合中是同质的,即每个集合都有相同的“ADD”、“REMOVE”、“RESET”等操作(但“集合”前缀不同;并且并非每个集合都必须实现所有这些操作),那么你可以进一步创建通用的操作生成器、通用的reducer生成器等,这样你就不必多次重复编写原本相同的代码。

编辑:根据Matthew的请求,这里有一个更高级的示例,它结合了泛型和NgRX实体**。

*不是该软件包的作者

**我是那个例子的作者。把完整的例子贴在这里有点笨重(而且超出了这个问题的重点),但如果分享那个链接不合适,请告诉我。

答案#2

提出的小幅升级脚手架

import { Action } from '@ngrx/store';
import { Animal } from 'animal.models';

export const ADD = '[Auth] Add';

export class Add implements Action {
  readonly type = ADD;
  constructor(public payload: Animal[]) {}
}

我在考虑实现一些更好的东西,真的,第一次使用 NGRX/redux/flux 的 CRUD 异步操作会让人害怕,我在使用 Apollo 团队内部的异步存储和同步选项,我觉得这是未来,它很好地用一个真实来源来管理选项,他们为 CRUD* 操作创建了通用动作机制,当你想做一些特殊操作时,你可以使用依赖注入并为存储编写不可变函数的 reducer,或者我觉得你可以使用 effects(我还没试过)

为什么我认为这是未来?Apollo 2.0 不需要你每次为 CRUD 操作重复编写代码,也不需要每次为捕获异步服务器操作写 effects,再加上 schema 可以通过 introspection 来保护你的类型到 TS(生成器已经可以用于生产)。我对 https://dev-blog.apollodata.com/whats-coming-in-apollo-client-2-0-bcd8ea64acbd(由 James Baxley III 撰写)以及“未来功能”段落非常乐观——当这个未来变成现实,可以实现一次编写 mutations、在不同服务器、浏览器、点对点实时同步状态,那将非常棒……