You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在NGRX Effect中统一处理末尾的通用Action(如加载状态)

问题描述

我开发的应用采用NGRX作为状态管理工具,现有一个Effect逻辑:根据ID从状态中查找对象,若未找到则调用服务器接口。两种场景都需要将当前对象设置到状态中,同时我需要让该Effect处理加载指示器(调用服务器时会存在延迟)。目前我已实现的Effect可正常运行,但我不满意两次调用setLoading Action的写法。尝试通过finalize(() => DealsApiActions.setLoading({ loadingDeals: false }))(包括配合this.store.dispatch())来触发该Action,但并未生效。请问有没有更简洁的方式,在这类可能存在延迟的Effect末尾统一处理通用Action?

解决方案

1. 用mergeMap + finalize 修正写法

你之前用finalize没生效,核心原因是**finalize的回调不会把返回的Action注入到Effect的输出流里**,必须直接调用store.dispatch才能触发。用这种写法可以彻底统一收尾逻辑,不用在每个分支重复写setLoading(false):

loadDeal$ = createEffect(() => 
  this.actions$.pipe(
    ofType(DealsActions.loadDeal),
    // 一开始先触发加载中状态
    tap(() => this.store.dispatch(DealsApiActions.setLoading({ loadingDeals: true }))),
    mergeMap(action => {
      // 从本地状态查对象
      const deal = this.store.selectSnapshot(selectDealById(action.id));
      
      if (deal) {
        // 本地找到,直接返回设置对象的Action
        return of(DealsActions.setCurrentDeal({ deal }));
      } else {
        // 本地没找到,请求接口后返回对应Action
        return this.dealsService.getDeal(action.id).pipe(
          map(deal => DealsActions.setCurrentDeal({ deal })),
          catchError(error => of(DealsActions.loadDealFailed({ error })))
        );
      }
    }),
    // 流完成/出错时统一关闭加载状态
    finalize(() => {
      this.store.dispatch(DealsApiActions.setLoading({ loadingDeals: false }));
    })
  )
);

2. 用concatMap 统一分支输出

如果不想依赖store.dispatch,可以让所有分支的流都追加收尾Action,通过concatMap把结果和收尾Action合并输出:

loadDeal$ = createEffect(() => 
  this.actions$.pipe(
    ofType(DealsActions.loadDeal),
    tap(() => this.store.dispatch(DealsApiActions.setLoading({ loadingDeals: true }))),
    concatMap(action => {
      const deal = this.store.selectSnapshot(selectDealById(action.id));
      
      if (deal) {
        // 本地找到:输出设置对象Action + 关闭加载Action
        return of(
          DealsActions.setCurrentDeal({ deal }),
          DealsApiActions.setLoading({ loadingDeals: false })
        );
      } else {
        // 本地没找到:请求接口后,把结果和关闭加载Action合并输出
        return this.dealsService.getDeal(action.id).pipe(
          map(deal => DealsActions.setCurrentDeal({ deal })),
          catchError(error => of(DealsActions.loadDealFailed({ error }))),
          concatMap(result => of(result, DealsApiActions.setLoading({ loadingDeals: false })))
        );
      }
    })
  )
);

3. 封装通用加载操作符(进阶)

如果多个Effect都要处理加载状态,直接封装一个自定义RxJS操作符,彻底消除重复代码:

// 自定义操作符:处理加载状态的通用逻辑
export function withLoading<T>(
  startAction: Action,
  endAction: Action,
  store: Store
): MonoTypeOperatorFunction<T> {
  return source => 
    source.pipe(
      tap(() => store.dispatch(startAction)),
      finalize(() => store.dispatch(endAction))
    );
}

然后在Effect里直接用:

loadDeal$ = createEffect(() => 
  this.actions$.pipe(
    ofType(DealsActions.loadDeal),
    // 直接复用通用加载逻辑
    withLoading(
      DealsApiActions.setLoading({ loadingDeals: true }),
      DealsApiActions.setLoading({ loadingDeals: false }),
      this.store
    ),
    mergeMap(action => {
      const deal = this.store.selectSnapshot(selectDealById(action.id));
      
      if (deal) {
        return of(DealsActions.setCurrentDeal({ deal }));
      } else {
        return this.dealsService.getDeal(action.id).pipe(
          map(deal => DealsActions.setCurrentDeal({ deal })),
          catchError(error => of(DealsActions.loadDealFailed({ error })))
        );
      }
    })
  )
);

内容的提问来源于stack exchange,提问作者Matt Brewerton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 18:05:29