Firestore监听器订阅随机多次触发,如何修复且保留文档变更监听能力?
问题根源
- 嵌套订阅导致订阅泄漏:在
authState回调中直接创建valueChanges订阅,未对旧订阅做销毁处理,即使authState仅触发一次,也可能生成多个重复订阅,每个订阅触发都会累加计数器。 - 无变更校验逻辑:
valueChanges会响应本地缓存同步、元数据更新等所有关联事件,即使文档内容没有实际修改也会触发回调,导致无效执行token刷新逻辑。 - 存在循环触发风险:在回调中调用
user.getIdToken(true)强制刷新token,若token刷新操作间接触发auth状态或用户文档的轻量更新,会形成调用循环,导致回调反复执行。
修复方案
重构订阅逻辑,使用RxJS高阶操作符管理订阅流,增加去重和生命周期管控:
import { Injectable, OnDestroy } from '@angular/core'; import { BehaviorSubject, Subject, takeUntil, distinctUntilChanged, switchMap } from 'rxjs'; import { AngularFireAuth } from '@angular/fire/compat/auth'; import { AngularFirestore } from '@angular/fire/compat/firestore'; import { Router } from '@angular/router'; interface SavedUser {/* 你的用户类型定义 */} interface User {/* 你的用户类型定义 */} @Injectable({ providedIn: 'root', }) export class AuthService implements OnDestroy { user$: BehaviorSubject<SavedUser | undefined> = new BehaviorSubject<SavedUser | undefined>(undefined); // 用于统一销毁订阅的触发器 private destroy$ = new Subject<void>(); constructor( private angularFireAuth: AngularFireAuth, private firestore: AngularFirestore, private router: Router ) { this.onAuthStateChanged(); } onAuthStateChanged() { let counter = 0; this.angularFireAuth.authState.pipe( // 自动切换/销毁上一个用户的Firestore订阅 switchMap(user => { if (!user) return []; return this.firestore .collection('user') .doc<User>(user.uid) .valueChanges() .pipe( // 仅当文档内容实际变更时才触发后续逻辑,做深度对比可自定义比对函数 distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)) ); }), // 服务销毁时自动取消整个流的订阅 takeUntil(this.destroy$) ).subscribe(userRecord => { counter++; console.log(counter); user.getIdToken(true); this.user$.next(userRecord); }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }
关键逻辑说明
switchMap会在每次authState返回新用户时,自动取消上一个用户的Firestore文档监听,彻底解决订阅累积泄漏的问题,不需要手动管理多个订阅实例。distinctUntilChanged通过比对前后两次返回的文档内容,过滤掉缓存同步、元数据更新等无效触发,只有当文档内容真实变更时才会执行回调。takeUntil配合destroy$在服务销毁时统一取消所有订阅,避免内存泄漏。
注意:如果确认用户文档没有手动更新但仍有重复触发,可检查
user.getIdToken(true)是否会触发云端函数修改用户文档,或者是否有其他地方同时修改该文档,避免形成调用循环。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

