问题:如何重构/抽象 ngrx 操作以减少拼写错误?

更新: 似乎 typescript-fsa 可能是我正在寻找的自以为是的库https://github.com/aikoven/typescript-fsa/issues/40。任何有关实施细节的帮助都会很棒!

我觉得当我在做ngrx时,我一直在复制和粘贴文件,然后只是重命名部分。这很容易出现我典型的胖手指错误。虽然我目前没有测试我的项目,但如果一个项目需要 100% 的代码覆盖率,这可能是一项更加乏味的任务。

一个简单的动作示例,称为 Counter:

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;

问题:

在打字稿中,是否有任何模式可以通过简单的函数调用来获得上面显示的这些类的功能?

一个例子是:

TypeAction.create('Animal', ['Add', 'Remove', 'Reset'])

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

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

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

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

解答

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

因此,您可以定义动作_generators_,而不是“本机”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');

然后生成的动作将被命名为“计数器/增量”等。

在你的减速器中使用它们,如下所示:

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 => ...)

如您所见,除了初始定义之外,您从不使用容易出现拼写错误的操作字符串,而是导入类型化操作生成器,这提供了额外的安全性,并且还可以在可识别打字稿的代码编辑器中进行函数替换。

如果您的操作是跨集合的_同质_,即每个集合都有_相同_“添加”、“删除”、“重置”等操作(但不同的“集合”前缀;并且并非每个集合都必须实现所有这些),那么您可以更进一步,创建 generic 动作生成器、generic reducer 生成器等,这样您就不必多次复制其他相同的代码。

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

*不是那个包的作者

**我是该示例的作者。在此处粘贴完整示例有点笨拙(并且超出了此问题的重点),但如果不适合共享该链接,请告诉我。

Logo

React社区为您提供最前沿的新闻资讯和知识内容

更多推荐