Angular13结合AngularFire7加载Firestore列表UI数据重复问题
问题根因
所有列表页重复展示数据、路由跳转返回后暂时恢复正常的问题,是多个逻辑疏漏共同导致的:
@angular/fire的docData()、collectionData()返回的是实时快照Observable流,不是单次HTTP类的拉取请求。只要订阅不终止,后续本地缓存同步、服务端数据校验、文档内容变更都会触发流推送新值,每次收到值就直接执行push,自然会重复插入数据。第一次进入页面时Firestore会先推本地缓存值、再推服务端同步后的最新值,哪怕只有1个teamId也会插入2条;跳转其他路由再返回时,本地缓存已经完成同步,短时间内不会触发两次推送,所以看起来暂时正常,但如果反复进出页面,重复条数会持续增加。- 加载数据前没有清空
teamsList数组,旧数据残留+新数据插入会进一步放大重复问题。 - 之前用
includes()做去重完全无效:Firestore每次推送返回的是新构造的对象,includes()对引用类型判断的是内存地址,哪怕两个对象内容完全一致,内存地址不同也会判定为不重复。 - 循环中用
var声明迭代变量存在作用域问题,异步订阅回调中拿到的索引值可能不符合预期,会进一步导致数据错乱。 - 循环内单独订阅流没有做统一的订阅管理,组件销毁时没有取消订阅,会造成内存泄漏,重复订阅也会导致重复插入。
修复方案
按优先级依次修改即可:
1. 加载数据前先重置目标数组
在loadTeamsList方法最开始清空旧数据,避免残留:
private loadTeamsList(){ if(!this.currentUser.teams) { this.teamsList = []; return; } // 每次加载前清空旧数据 this.teamsList = []; // 后续逻辑替换 }
2. 替换错误的去重逻辑
不要直接对对象做includes()判断,用数据唯一主键id做重复校验:
// 错误写法:引用类型判断失效 // if (this.teamsList.includes(team)) return // 正确写法:用唯一ID判断 if (this.teamsList.some(item => item.id === team.id)) return
3. 正确管理订阅,根据业务场景选择流处理方式
场景A:不需要实时更新,仅需拉取一次数据
给流加take(1)操作符,拿到第一个值(本地缓存/服务端值均可)后自动终止订阅,不会接收后续重复推送:
// 顶部导入rxjs操作符 import { take } from 'rxjs/operators'; // 循环内的订阅修改为 // 注意把var改成let,修复作用域问题 for(let i = 0;i < this.currentUser.teams.length; i++){ this.teamsService.getTeam(this.currentUser.teams[i]) .pipe(take(1)) .subscribe( team => { if(this.teamsList.some(item => item.id === team.id)) return; this.teamsList.push(team); }) }
场景B:需要实时监听团队数据变更
不要手动遍历订阅+push填充数组,用combineLatest组合所有文档流,直接将最新的完整数组赋值给teamsList,从根源避免手动插入导致的重复,同时用takeUntil在组件销毁时取消订阅,防止内存泄漏:
// 顶部导入依赖 import { combineLatest, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; // 组件内添加销毁标记 private destroy$ = new Subject<void>(); ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } // 重写loadTeamsList private loadTeamsList(){ if(!this.currentUser.teams?.length) { this.teamsList = []; return; } this.teamsList = []; // 构造所有团队的数据流 const teamStreamList = this.currentUser.teams.map(teamId => this.teamsService.getTeam(teamId) ); // 组合所有流,任意一个团队数据变更都会返回最新的完整数组 combineLatest(teamStreamList) .pipe(takeUntil(this.destroy$)) .subscribe(latestTeams => { // 直接赋值替换,不要push this.teamsList = latestTeams; }) }
内容的提问来源于stack exchange,提问作者Joe Iacono
相关产品推荐
相关产品推荐

