有没有办法一次发送一个动作?

Is there a way to dispatch an action once?

详情

我一直在做一个项目,我从 API 获取数据。为了获取这些数据,我将我需要的东西存储在商店中。 例如,在商店中,我正在存储当前选择的时间段、地图的当前 bbox 以及从 API 中获取的数据。我需要实现的是每次选中的时间段或者地图的当前边界框发生变化时更新store中的数据。

为此,我创建了一个 Action、一个 Reducer 和一个 Effect for Periods、BBox 和 Stats。我遵循的逻辑如下,我也将其用于 BBox 和 Stats。

代码

export interface IPeriodState {
  periods: Period[];
  isLoading: boolean;
  error: Error;
}

在减速器中,我只是改变状态,仅此而已。我的行为在这一点上是相同的逻辑:

import { Action } from "@ngrx/store";
import { Period } from "../../models/Period";

export enum PeriodActionTypes {
  ADD_PERIOD = "[Period] Add Period",
  ADD_PERIOD_SUCCESS = "[Period] Add Period Success",
  ADD_PERIOD_FAILURE = "[Period] Add Period Failure",

  REMOVE_PERIOD = "[Period] Remove Period",
  REMOVE_PERIOD_SUCCESS = "[Period] Remove Period Success",
  REMOVE_PERIOD_FAILURE = "[Period] Remove Period Failure",

  UPDATE_PERIOD = "[Period] Update Period",
  UPDATE_PERIOD_SUCCESS = "[Period] Update Period Success",
  UPDATE_PERIOD_FAILURE = "[Period] Update Period Failure"
}

// Add Period
export class AddPeriodAction implements Action {
  readonly type = PeriodActionTypes.ADD_PERIOD;
  constructor(public payload: Period) {}
}

export class AddPeriodSuccessAction implements Action {
  readonly type = PeriodActionTypes.ADD_PERIOD_SUCCESS;
  constructor(public payload: Period) {}
}

export class AddPeriodFailureAction implements Action {
  readonly type = PeriodActionTypes.ADD_PERIOD_FAILURE;
  constructor(public error: Error) {}
}

// Remove Period
export class RemovePeriodAction implements Action {
  readonly type = PeriodActionTypes.REMOVE_PERIOD;
  constructor(public id: string) {}
}

export class RemovePeriodSuccessAction implements Action {
  readonly type = PeriodActionTypes.REMOVE_PERIOD_SUCCESS;
  constructor(public id: string) {}
}

export class RemovePeriodFailureAction implements Action {
  readonly type = PeriodActionTypes.REMOVE_PERIOD_FAILURE;
  constructor(public error: Error) {}
}

// Update Period
export class UpdatePeriodAction implements Action {
  readonly type = PeriodActionTypes.UPDATE_PERIOD;
  constructor(public id: string, public payload: Period) {}
}

export class UpdatePeriodSuccessAction implements Action {
  readonly type = PeriodActionTypes.UPDATE_PERIOD_SUCCESS;
  constructor(public id: string, public payload: Period) {}
}

export class UpdatePeriodFailureAction implements Action {
  readonly type = PeriodActionTypes.UPDATE_PERIOD_FAILURE;
  constructor(public error: Error) {}
}

export type PeriodActions =
  | AddPeriodAction
  | AddPeriodSuccessAction
  | AddPeriodFailureAction
  | RemovePeriodAction
  | RemovePeriodSuccessAction
  | RemovePeriodFailureAction
  | UpdatePeriodAction
  | UpdatePeriodSuccessAction
  | UpdatePeriodFailureAction;

实际上,我正在调度多个操作。例如:

//imports omitted

@Injectable()
export class PeriodEffect {
  constructor(private actions: Actions) {}

  @Effect() addPeriod$ = this.actions.pipe(
    ofType<PeriodActions>(PeriodActionTypes.ADD_PERIOD),
    mergeMap((action: AddPeriodAction) => [
      new AddPeriodSuccessAction(action.payload),
      new LoadStatsAction()
    ]),
    catchError(error => of(new AddPeriodFailureAction(error)))
  );

  @Effect() removePeriod$ = this.actions.pipe(
    ofType<PeriodActions>(PeriodActionTypes.REMOVE_PERIOD),
    mergeMap((action: RemovePeriodAction) => [
      new RemovePeriodSuccessAction(action.id),
      new LoadStatsAction()
    ]),
    catchError(error => of(new RemovePeriodFailureAction(error)))
  );

  @Effect() updatePeriod$ = this.actions.pipe(
    ofType<PeriodActions>(PeriodActionTypes.UPDATE_PERIOD),
    mergeMap((action: UpdatePeriodAction) => [
      new UpdatePeriodSuccessAction(action.id, action.payload),
      new LoadStatsAction()
    ]),
    catchError(error => of(new UpdatePeriodFailureAction(error)))
  );
}

问题

我的问题是 LoadStatsAction 多次发送,因为我需要在商店中获取新数据,每次网站发生变化时。让我们看看我的 period-selector 组件。一旦期间发生变化,我将使用以下代码管理此更改:

constructor(private moment: MomentPipe, private store: Store<AppState>) {
  this.currentPeriod = new Period(
    id(),
    new Date(2017, 9, 1),
    new Date(2018, 9, 1)
  );

  this.store.dispatch(new AddPeriodAction(this.currentPeriod));
}

ngOnInit() {}

ngOnDestroy() {
  this.store.dispatch(new RemovePeriodAction(this.currentPeriod.id));
}

updateCurrentPeriod(period: IPeriod) {
    this.currentPeriod._minDate = period.minDate;
    this.currentPeriod._maxDate = period.maxDate;
    this.store.dispatch(
      new UpdatePeriodAction(this.currentPeriod.id, this.currentPeriod)
    );
  }

总结

有没有办法摆脱多次派发的动作?

备注

您可以找到项目的完整代码here and a demo of the project here

我建议提取何时调度 LoadStatsAction 操作的逻辑以避免重复代码,它应该如下所示:

  @Effect() addPeriod$ = this.actions.pipe(
    ofType<PeriodActions>(PeriodActionTypes.ADD_PERIOD),
    map((action: AddPeriodAction) => new AddPeriodSuccessAction(action.payload)),
    catchError(error => of(new AddPeriodFailureAction(error)))
  );

  @Effect() removePeriod$ = this.actions.pipe(
    ofType<PeriodActions>(PeriodActionTypes.REMOVE_PERIOD),
    map((action: RemovePeriodAction) => new RemovePeriodSuccessAction(action.id)),
    catchError(error => of(new RemovePeriodFailureAction(error)))
  );

  @Effect() updatePeriod$ = this.actions.pipe(
    ofType<PeriodActions>(PeriodActionTypes.UPDATE_PERIOD),
    map((action: UpdatePeriodAction) => new UpdatePeriodSuccessAction(action.id, action.payload)),
    catchError(error => of(new UpdatePeriodFailureAction(error)))
  );

  @Effect() loadStats$ = this.actions.pipe(
      ofType(
        AddPeriodSuccessAction,
        RemovePeriodSuccessAction,
        UpdatePeriodSuccessAction
      ),
      mapTo(new LoadStatsAction())
  )

现在您可以专注于减少调度 LoadStatsAction 的次数,使用 debounceTime(x) or throttleTime(x) (可以找到这些运算符之间的区别

如果我的问题是对的,我认为问题是您想要多次分派操作,但不想将其添加到 3 个效果中?如果是这种情况,您可以执行以下操作:

我们可以在ofType操作符中监听多个动作,当当前请求还没有完成时,使用switchMapexhaustMap取消多个请求。

  @Effect() addPeriod$ = this.actions.pipe(
    ofType(PeriodActionTypes.ADD_PERIOD_SUCCESS, PeriodActionTypes.REMOVE_PERIOD_SUCCESS, UPDATE_PERIOD_SUCCESS),
    switchMap((action) => {
       // logic here to load stats
       // or to dispatch the LoadStatsAction action
    }),
  );