【问题标题】:Is there a way to dispatch an action once?有没有办法一次分派一个动作?
【发布时间】:2019-12-10 11:19:04
【问题描述】:

详情

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

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

代码

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

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

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和项目的演示here

【问题讨论】:

  • 我们能否为updatePeriodSuccess$ 再创建一个效果,并且我们可以随时使用 switchMap 或 exhaustMap 到 LoadStatsAction 并取消/减少调用?
  • 我对 loadStats 有影响,就在这里。这是你想说的吗?而不是 mergeMap 使用switchMapexhaustMap@Effect() loadStats$ = this.actions.pipe( ofType&lt;StatsActions&gt;(StateActionTypes.LOAD_STATS), mergeMap(() =&gt; this.apiService.getGlobalStats().pipe( map((data: IGlobalStats) =&gt; new LoadStatsSuccessAction(data)), catchError(error =&gt; of(new LoadStatsFailureAction(error))) ) ) );

标签: angular rxjs ngrx ngrx-effects


【解决方案1】:

我建议提取何时调度 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)throttleTime(x) (这些运算符之间的区别可以找到here

【讨论】:

  • 是的,这就是我要找的。​​span>
【解决方案2】:

如果我的问题是正确的,我认为问题是您想多次分派动作,但又不想将其添加到 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
    }),
  );

【讨论】:

  • 是的,你的问题是正确的。我会试一试,让你知道这是否有效
  • 它可以按我的意愿工作,但我发现@tal-ohana 的回答更清晰易懂
猜你喜欢
  • 2017-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-15
  • 1970-01-01
  • 2022-12-10
  • 2020-05-05
  • 2011-08-01
相关资源
最近更新 更多