如何同时调度多个 ngrx 动作

Posted

技术标签:

【中文标题】如何同时调度多个 ngrx 动作【英文标题】:How to dispatch multiple ngrx actions at the same time 【发布时间】:2020-01-25 04:07:24 【问题描述】:

我正在使用 ngrx 并且有一个场景,我需要同时调度 2 个动作。我的状态具有更新和更新的属性,如下所示。

//from reducer
const defaultCardState: CardState = 
    ids: [],
    entities: ,
    loaded: false,
    loading: false,
    adding: false,
    added: false,
    updating: false,
    updated: false,
    deleting: false,
    deleted: false
;

这些是我从组件中调度的操作

this.store.dispatch(fromCard.updateCard(id: id1, changes: name: name1))
this.store.dispatch(fromCard.updateCard(id: id2, changes: name: name2))

下面是我的动作、reducer和效果

//Update Card Actions
export const updateCard = createAction('[Cards] Update Card', props<id: string, changes: any>())
export const updateCardSuccess = createAction('[Cards] Update Card Success', props<changes: any>());
export const updateCardFail = createAction('[Cards] Update Card Fail')

//Reducer
on(fromCards.updateCard, (state) => (...state, updating: true, updated: false)),
    on(fromCards.updateCardSuccess, (state, action: any) => (...cardAdapter.updateOne(action.changes, state), updated: true, updating: false)),
    on(fromCards.updateCardFail, (state, action: any) => fromCards.updateCardFail),

//Update Card Effect
updateCard$: Observable<Action> = createEffect(() => this.actions$.pipe(
    ofType(fromCardActions.updateCard),
    map((action: any) =>  return id: action.id, changes: action.changes),
    switchMap((action: any) => this.cardService.updateCard(action).pipe(
        map((res) => (fromCardActions.updateCardSuccess(changes: action ))),
        catchError(() => of(fromCardActions.updateCardFail))
    ))
))

依次调度这些操作以使更新和更新字段不冲突的最佳方式是什么?如果我只运行其中一个,它可以工作,但如果我像上图所示将它们一起发送,则只有一个完成。我看到两个动作都被分派了,但只有一个成功动作被分派。

【问题讨论】:

【参考方案1】:

你没有,你有一个不同的动作,有效载荷采用一组卡片而不是一张卡片,然后减速器返回使用多张卡片更新的新状态。您的 api 还应该能够接受一个数组,以便您的效果可以发送多个到服务器。

【讨论】:

谢谢!所以我会创建一个接受数组的服务,并对后端进行 n 次调用以进行更改? 让您的 api 接受数组的目的是,您可以在一次调用中批量更新,而不是往返服务器。【参考方案2】:

您可以在效果中分派多个操作,我建议您仅在效果中执行此操作

考虑下面的例子

@Effect()
dispatchMultiAction$: Observable<Action> = this.actions$.pipe(
    ofType<SomeAction.Dispatch>(someActions.Dispatch),
    switchMap(_ =>
        of(
            new someActions.InitData(),
            new someActions.GetData(),
            new someActions.LoadData()
        )
    )
);

【讨论】:

使用mergeMap 这样做更简洁。【参考方案3】:

从技术上讲,使用动作来调度另一个多个动作是非常糟糕的做法。它增加了应用程序的复杂性。一段时间后,您无法调试和理解为什么会分派动作。这段视频 Mike Ryan 用 NgRx 描述了良好的行动卫生: https://youtu.be/JmnsEvoy-gY?t=285

【讨论】:

链接到 YouTube 视频不是一个可接受的答案。评论很好,但这不是答案。至少解释一下视频中的内容,然后链接到视频作为参考。 @AdrianBrand 谢谢你的建议。我改变了答案。我分享了这个链接,因为我认为这是了解 ngrx 中动作架构的非常重要的视频。 谢谢!这段视频实际上非常令人大开眼界。我一直在重用动作,并从中看到了一些陷阱。所以他提到独特的事件动作会创建更多动作,但它们也会创建同样多的效果和减速器,对吗?【参考方案4】:

与托尼的回答类似,但使用了正确的运算符:

@Effect()
dispatchMultiAction$: Observable<Action> = this.actions$.pipe(
    ofType<SomeAction.Dispatch>(someActions.Dispatch),
    mergeMap(_ => [
            new someActions.InitData(),
            new someActions.GetData(),
            new someActions.LoadData()
        ])
    )
);

【讨论】:

您没有使用“of”运算符是否有原因。我不明白为什么它有时用于调度操作,有时不用于。 你可以考虑阅读 learnrxjs。他们对每个运营商都有很好的描述。 谢谢。我想我理解 of 运算符的作用,但我不确定为什么有时动作会像 Tony 上面展示的那样作为 observable 发送,而有时不像你的示例中那样 Tony 使用 of([]) 的唯一原因是因为 switchMap 只会返回一个 observable。从本质上讲,他所做的是一种返回多个操作的解决方法。但是,使用mergeMap 我们可以正确返回多个操作。【参考方案5】:

类似于 xandermonkey 的答案,但使用正确的运算符:

@Effect()
dispatchMultiAction$: Observable<Action> = this.actions$.pipe(
    ofType<SomeAction.Dispatch>(someActions.Dispatch),
    concatMap(_ => [
            new someActions.InitData(),
            new someActions.GetData(),
            new someActions.LoadData()
        ])
    )
);

注意 concatMap 和 mergeMap 的区别。因为 concatMap 在前一个完成之前不会订阅下一个 observable,所以延迟 2000ms 的来自源的值将首先发出。与立即订阅内部 observable 的 mergeMap 相比,延迟较小(1000 毫秒)的 observable 将发出,然后是需要 2000 毫秒才能完成的 observable。

【讨论】:

以上是关于如何同时调度多个 ngrx 动作的主要内容,如果未能解决你的问题,请参考以下文章

在调度一个动作和 NgRx 效果之前执行多个 API 调用

ngrx 调度的动作未达到效果

ngrx 8影响其他类型的调度动作

ngrx 效果:由一个效果调度的动作是不是立即被其他效果处理?

Ngrx - 减速器无法接收动作

如何调度一个空动作?