如何在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
相关产品推荐
相关产品推荐

