如何使用多个 NGRX 操作来按照"action hygiene"指南执行相同的操作?



如果我有两个或多个NGRX操作做相同的事情,但在应用程序的不同部分被触发,我可以使用一个操作来执行此操作,同时区分NGRX日志中的操作字符串吗?我之所以这么问,是因为在编写动作并试图遵循"动作卫生"原则时,我想尽可能多地限制NGRX的样板。在这些原则中,动作应该是特定于某个事件的,并且一年后可以阅读,并告诉开发者该动作在应用程序中的确切位置。

例如:我有一个"AddTodo"动作。这个操作可以在我的应用程序中的两个位置执行——一个在"Todo List"页面上,另一个在一个"Todo Details"页面上。每个动作对于触发它们的页面都是唯一的,但它们做的事情完全相同;他们将一个新的Todo项目添加到我的状态对象中。

const TODO_LIST = '[Todo List]';
const TODO_DETAILS = '[Todo Details]';
export const TODO_LIST_ADD = `${TODO_LIST} Add`;
export const TODO_DETAILS_ADD = `${TODO_DETAILS} Add`;
export class TodoListAdd implements Action {
readonly type = TODO_LIST_ADD;
constructor(public payload: Todo) { }
}
export class TodoDetailsAdd implements Action {
readonly type = TODO_DETAILS_ADD;
constructor(public payload: Todo) { }
}
export function todoReducer(state = initialState, action: Action): TodoState {
switch (action.type) {
case actions.TODO_LIST_ADD:
case actions.TODO_DETAILS_ADD:
return {
...state,
todo: (action as actions.TodoListAdd | actions.TodoDetailsAdd).payload,
}
default:
return state;
}
}

如果想保持行动卫生,这是唯一的方法吗?有两个操作做着完全相同的事情,唯一的区别是在NGRX事件日志中区分它们的字符串类型。我希望有一种方法可以通过耦合动作创作者来减少样板的数量,而不会失去类型的特殊性。

谢谢,

是的,如果你遵守良好的行动卫生,这是唯一的方法。

但是,您可以按照此处所述为您的操作提供上下文https://medium.com/javascript-everyday/reusable-action-creators-with-context-2a31dca98192.

经过几次尝试,在接受的答案中实现了类似的东西,并且在:

https://indepth.dev/posts/1407/force-good-action-hygiene-and-write-less-actions-in-ngrx-with-the-prepared-events-pattern

我找到了一个更适合我的替代解决方案,并认为将其发布在这里会很好,以防将来对任何人都有帮助。我从ngrx GitHub:的一个关于处理这样的案件的问题中得到了这个想法

https://github.com/ngrx/platform/issues/2902#issuecomment-771194540

在解决了一些eslint/typescript错误后,我提出了以下解决方案:

  1. 创建一些助手来处理具有特殊kind属性的操作。此属性可用于整个应用程序的各种操作,使Good Action Hygiene更加干燥:
export type CategoryAction = Action & { kind: string; };
export function hasKind({ kind }: { kind: string }) {
return {
kind
};
}
export function ofKind<T extends CategoryAction>(kind: string): OperatorFunction<Action, T> {
return pipe(
filter(action => ((action as unknown) as CategoryAction)?.kind === kind),
map(action => action as T)
);
}
  1. 为您希望在整个应用程序的多个位置使用的每种动作类型创建一个mixin:
export function hasNewTodo({ todo }: { todo: Todo }) {
return {
todo,
...hasKind({ kind: 'ADD_TODO' })
};
}
  1. 添加一个效果,将任何此类动作转换为单个动作:
// In your actions file
const addTodo = createAction(
'[Todo API] Create Todo',
props<{ todo: Todo }>()
);
// In your effects class
addTodo$ = createEffect(() => {
return this.actions$.pipe(
ofKind<CategoryAction & { todo: Todo }>('ADD_TODO'),
concatMap(action => {
return of(fromActions.addTodo({ todo: action.todo }));
})
);
});
  1. 为效果中的动作添加一个减速器:
const todoReducer = createReducer(
initialState,
on(fromActions.addTodo, (state, action) => {
return {
...state,
todos: [
...state.todos,
action.todo
]
};
})
);
  1. 在整个应用程序中添加您需要的任何操作:
// Near your TodoList component
export const addTodo = createAction(
'[Todo List] Add Todo',
(payload: { todo: Todo}) => ({
...hasNewTodo({ todo: payload.todo })
})
);
// Near your TodoDetails component
export const addTodo = createAction(
'[Todo Details] Add Todo',
(payload: { todo: Todo}) => ({
...hasNewTodo({ todo: payload.todo })
})
);

优点:

  • 最少的额外代码允许在整个应用程序中执行类似操作
  • 使用普通的ngrx构造,而不需要全部重写/替换它们
  • 遵循打开/关闭的原则:添加这种新动作不需要编辑效果或减速器,只需添加一个动作

缺点:

  • 需要额外的";翻译";每个类别的效果
  • 每次添加Todo时,该操作的日志中将显示两个不同的操作

最新更新