Angular中能否在constructor与ngOnInit分发请求Action?重复数据问题咨询
我来帮你捋捋这个问题——你遇到的情况大概率是NgRx使用中的几个常见误区导致的,咱们一步步拆解解决:
1. 先排查Reducer是否忽略了重复Payload的Action
很多时候开发者会在Reducer里加判断:如果新Action的payload和当前状态里的一致,就直接返回旧状态,避免重复请求。但这也会导致你主动刷新时,dispatch的Action被Reducer直接忽略。
你可以看看处理GetUserProfilesRequested的Reducer代码,是不是有类似这样的逻辑:
case GetUserProfilesRequested.type: // 如果当前状态的邮箱和请求邮箱一致,直接返回旧状态 if (state.currentUserEmail === action.payload) { return state; } return { ...state, loading: true };
如果是这样,就算你重复dispatch相同userEmail的Action,Reducer也不会触发后续流程。解决方法有两种:
- 直接去掉这个判断(如果你确实需要每次都强制刷新数据)
- 给Action添加唯一标识(比如时间戳),让Reducer认为每次都是新请求:
// 修改Action定义 export class GetUserProfilesRequested implements Action { readonly type = '[Profile] Get User Profiles Requested'; constructor(public payload: { email: string; timestamp: number }) {} } // 调用时传入时间戳 this.store.dispatch(new GetUserProfilesRequested({ email: userEmail, timestamp: Date.now() }));
2. 修复订阅逻辑,避免重复订阅或订阅失效
你每次调用getProfileDatafromStore都会重新订阅store.select(getUserProfile),但如果unsubscribe$是组件销毁时才触发的Subject,会导致多个订阅同时存在,甚至某次订阅被提前取消后无法接收新数据。
推荐用两种方式优化:
方式一:手动管理单次请求的订阅
// 新增一个Subject用于管理当前请求的订阅 private currentProfileSub$ = new Subject<void>(); public getProfileDatafromStore() { // 先取消上一次的订阅,避免重复监听 this.currentProfileSub$.next(); this.store.dispatch(new GetUserProfilesRequested(userEmail)); this.store.select(getUserProfile) .pipe( takeUntil(this.currentProfileSub$), // 只监听当前请求的结果 takeUntil(this.unsubscribe$), // 保留组件销毁时的全局取消 filter(val => !!val) // 过滤空值,避免无效处理 ) .subscribe(storeval => { this.userProfileData = storeval; this.loginProfile(this.userProfileData); }); } // 组件销毁时清理所有订阅 ngOnDestroy() { this.unsubscribe$.next(); this.unsubscribe$.complete(); this.currentProfileSub$.next(); this.currentProfileSub$.complete(); }
方式二:用switchMap优雅管理请求流
这种方式不用手动处理订阅,让RxJS自动管理:
// 新增一个Subject作为刷新触发源 private reloadProfileTrigger$ = new Subject<string>(); constructor(private store: Store<AppState>) { // 初始化时就构建好数据流 this.userProfiles$ = this.reloadProfileTrigger$.pipe( switchMap(email => { // 每次触发都dispatch新请求 this.store.dispatch(new GetUserProfilesRequested(email)); // 只取这次请求返回的有效数据 return this.store.select(getUserProfile).pipe( filter(val => !!val), take(1) ); }), takeUntil(this.unsubscribe$) ); // 统一订阅数据变化 this.userProfiles$.subscribe(storeval => { this.userProfileData = storeval; this.loginProfile(this.userProfileData); }); // 处理filterService的监听 this.filterService.dashboardModified.subscribe(val => { if (this.filterService.IsdashboardModified === "true") { this.reloadProfileTrigger$.next(userEmail); } }); } ngOnInit() { // 初始化时触发第一次加载 this.reloadProfileTrigger$.next(userEmail); }
3. 检查Effects的缓存逻辑
如果你的请求是由NgRx Effects处理的,看看Effects里是不是加了缓存判断,比如已有数据就不发起新请求:
@Effect() getUserProfiles$ = this.actions$.pipe( ofType(GetUserProfilesRequested.type), withLatestFrom(this.store.select(getUserProfile)), // 如果已有数据就跳过请求 filter(([action, profile]) => !profile), switchMap(([action]) => { return this.userService.getProfile(action.payload).pipe( map(data => new GetUserProfilesSucceeded(data)), catchError(err => of(new GetUserProfilesFailed(err))) ); }) );
如果是这样,你可以在刷新前先清空旧数据:
public getProfileDatafromStore() { // 先清空状态里的旧数据 this.store.dispatch(new ClearUserProfiles()); // 再发起新请求 this.store.dispatch(new GetUserProfilesRequested(userEmail)); }
记得在Reducer里处理ClearUserProfiles Action,把userProfile设为null或undefined。
4. 验证filterService的触发逻辑
最后可以加个日志,确认filterService的订阅是否正确触发:
this.filterService.dashboardModified.subscribe(val => { console.log('dashboardModified触发:', val, '当前IsdashboardModified:', this.filterService.IsdashboardModified); if (this.filterService.IsdashboardModified === "true") { this.getProfileDatafromStore(); } });
另外注意IsdashboardModified是字符串类型的"true",如果实际是布尔值的话,判断要改成this.filterService.IsdashboardModified === true,避免类型不匹配导致的判断失效。
内容的提问来源于stack exchange,提问作者vinoba

