首页 > 解决方案 > 重用reducers逻辑

问题描述

问题

我想使用ngrx链接多个高阶reducer,这样我reducer中类似的代码部分只有一个实现。

我的应用程序有许多页面具有非常相似的功能。他们的减速器看起来也很相似。对于此示例,让我们考虑一个具有三页的应用程序:第一页、第二页和第三页。这些页面中的每一个都包含一个计数器,但允许用它做不同的事情。所以:

可以在此处找到此类应用程序的示例。这是非常幼稚的实现,每个页面都有单独的减速器 - 每个减速器中重复了许多非常相似的功能。

仅使用一台高阶减速机的解决方案

我设法使用高阶化简器将部分通用逻辑移动到分离代码段。在这种情况下,它是增量功能:

interface Actions {
  incrementAction: ActionCreator<string>;
}

export const withIncrementation = ({ incrementAction }: Actions) => (
  initState,
  ...actions
) =>
  createReducer(
    initState,
    on(incrementAction, state => ({
      ...state,
      counter: state.counter + 1
    })),
    ...actions
  );

这个高阶减速器可以这样使用:

const pageOneReducer = withIncrementation({
  incrementAction: PageOneActions.IncrementRequested
})(
  initialState,
  on(PageOneActions.DecrementRequested, (state: State) => ({
    ...state,
    counter: state.counter - 1
  }))
);

export function reducer(state: State | undefined, action: Action) {
  return pageOneReducer(state, action);
}

到目前为止,一切正常。像这样正常工作的应用程序在这里

使多个高阶减速器工作的问题

当我尝试链接多个高阶减速器工作时,问题就开始了。在示例应用程序中,第二第三页都能够重置计数器(也可以递增),所以我现在想使用 2 个高阶减速器。我准备了新的高阶减速器,与前一个非常相似,应该可以完成工作:

export const withReseting = ({ resetAction }: Actions) => (
  initState,
  ...actions
) =>
  createReducer(
    initState,
    on(resetAction, state => ({
      ...state,
      counter: initialState.counter
    })),
    ...actions
  );

它单独工作时工作正常,但是我找不到一种方法让它以这样的方式工作,withIncrementationwithReseting在同一个减速器上工作正常。可以在此处找到我尝试实现的示例应用程序,但它不起作用(似乎我的状态根本停止工作)。

使用 3 个独立减速器的简单方法

单台高阶减速机工作正常

链接不起作用的高阶减速器

标签: angularngrxhigher-order-functionsngrx-reducers

解决方案


我遇到了您的问题,因为我试图在我的应用程序中实现类似的东西。我正在从我的后端 API 访问很多实体,它们都是相似的,并且需要相似的减速器。

您的帖子很有帮助,因为它向我展示了一种行不通的方法:即链接减速器。这让我开始思考:如果我们坚持使用单个 reducer,但使用高阶函数生成怎么办?ons()

助手类型

首先,我创建了一个小的 Action 帮助器类型,以便更轻松地使用参数配置 Actions:
export type ActionWithParams<T> = ActionCreator<string, (props: T) => T & TypedAction<string>>;

高阶函数

然后我创建了我的高阶函数。对于这个例子,我们将生成三个 Action Reducer,其中两个将有参数。您的函数可以根据需要包含尽可能多或尽可能少的 Action Reducer。请注意,我使用的是通用 State 类型,如您所料,这意味着所有使用此函数的 reducer 必须共享一个相似的 State。显然,你也可以在这里硬编码一个特定的 State 类型。
interface MyActionsConfig {
  action1: ActionWithParams<{param: string}>;
  action2: ActionWithParams<{param: boolean}>;
  action3: ActionCreator<string>;
}

export const createMyReducerOns = <T>(config: MyActionsConfig): ReducerTypes<MyState<T>, ActionCreator[]>[] => {
  return [
    on(config.action1, (state, action): MyState<T> => ({
      ...state, stringProperty: action.param
    })),
    on(config.action2, (state, action): MyState<T> => ({
      ...state, booleanProperty: action.param
    }),
    on(config.action3, (state): MyState<T> => ({
      ...state, someOtherProperty: false
    })
  ];
}

减速机

最后,我们来实际创建reducer。这最终看起来很像正常(这是假设您已经定义了一个initialState)。这里很酷的是,您可以轻松混合和匹配多个生成函数(我相信这与您的初始场景完美匹配),当然还可以像往常一样on()创建一次性的。on()

import * as myActions from './my-actions'
export const myReducer = createReducer(
  initialState,
  ...createMyReducerOns<MyType>({
    action1: myActions.action1,
    action2: myActions.action2,
    action3: myActions.action3
  }).
  ...anotherCreateReducerOns<MyType>({
    action1: myActions.differentAction1,
    action2: myActions.differentAction2,
    action3: myActions.differentAction3
  }),
  on(myActions.uniqueAction, (state, action): MyState<T> => {
    ...state, superSpecialField: action.value
  })
);

注意:您甚至可以通过配置接口传入EntityAdapter<MyType>from的实例@ngrx/entity(如果您正在使用此库)并使用所有adapter()方法。


推荐阅读