NgRx条件加载数据的Effect中如何优雅处理loading标记?
NgRx纯事件模式下loading状态的维护方案
你的思路完全正确:不应该在memberListInitialized对应的Reducer中设置loading状态。这个Action仅代表「列表组件完成初始化」这个事实,经过Effect中的加载状态过滤后,该动作不一定会触发实际接口请求,如果直接在这里把loading设为true,会出现无请求但loading状态持续为true的bug。
最贴合现有纯过去式Action设计模式的实现方式,是在Effect中确定要发起请求的节点,新增一个代表「成员加载已启动」的Action,仅在真正触发接口调用前派发该动作,Reducer只响应真实发生的事件更新状态即可。
具体实现步骤
- 新增两个状态类Action,均为已发生的事实,符合纯事件设计规范:
membersLoadStarted:成员数据加载流程已正式启动membersLoadFailed:成员数据加载已失败(用于异常场景下重置loading状态,避免loading卡死)
- 调整原有Effect逻辑,在通过
loaded校验、确定要发起请求后,先派发加载启动动作,再执行接口请求:
getAllMembers$ = createEffect( () => this.actions$ .pipe( ofType(MemberActions.memberListInitialized), concatLatestFrom(() => this.store.select(MemberSelectors.isLoaded)), filter(([_, loaded]) => !loaded), // 执行到此处已100%确定会发起接口请求 switchMap(() => { return concat( // 优先派发加载启动动作,同步更新loading状态 of(MemberActions.membersLoadStarted()), // 执行实际接口请求 this.memberService.getAll().pipe( map((members) => MemberActions.membersLoaded({members})), // 异常场景派发失败动作,重置loading catchError((error) => of(MemberActions.membersLoadFailed({error}))) ) ) }) ) );
- 调整Reducer逻辑,完全不需要处理
memberListInitialized动作,仅对真实的加载生命周期事件做状态更新:
export const initialState: MemberState = { members: [], loading: false, loaded: false, error: null }; export const memberReducer = createReducer( initialState, on(MemberActions.membersLoadStarted, (state) => ({ ...state, loading: true, loaded: false, error: null })), on(MemberActions.membersLoaded, (state, { members }) => ({ ...state, loading: false, loaded: true, members, error: null })), on(MemberActions.membersLoadFailed, (state, { error }) => ({ ...state, loading: false, loaded: false, error })) );
方案优势
- 完全符合你当前的架构设计:所有业务判断逻辑(是否需要重新加载数据、异常处理)全部下沉到Effects层,组件层依然只需要派发
memberListInitialized动作,不需要感知任何加载逻辑 - 状态一致性有保障:loading状态只会在真正发起请求前置为true,请求成功、失败都会及时重置,不会出现状态和实际行为不匹配的问题
- 所有Action均为代表已发生事实的过去式命名,没有混入命令式动作,保持了设计模式的统一
- Reducer保持纯函数特性,只根据传入的Action事件做状态更新,不承载任何判断逻辑
注意不要遗漏请求失败的分支处理,否则接口报错时loading状态会一直卡在true,影响页面交互。
内容的提问来源于stack exchange,提问作者Tim Hardy
相关产品推荐
相关产品推荐

