在RTK Query中取消Firebase onSnapshot订阅及解决重渲染问题
解决方案:RTK Query结合Firebase实时监听的正确姿势
一、解决内存泄漏问题
你最初的写法会导致内存泄漏,因为queryFn里的onSnapshot订阅没有被取消——组件卸载后,Firebase仍然会持续推送更新,同时Promise已经resolve,无法管理订阅生命周期。
RTK Query本身提供了**onCacheEntryAdded生命周期钩子**来处理这类实时流场景,这是官方推荐的方案,不需要换其他Redux工具。它内置了cacheEntryRemoved信号,能在缓存失效(比如组件卸载、查询参数变化)时自动触发,用来清理订阅。
二、修复未触发重渲染的问题
你后来的代码里,updateCachedData的写法错误,导致Immer无法追踪到缓存更新,所以组件没触发重渲染:
错误原因:
updateCachedData((draft) => { draft = []; // 直接给draft赋值,Immer无法捕获这个修改 draft.push(players); // 这里push的是整个players数组,会变成二维数组 });
Immer要求你直接修改draft的内容,而不是重新赋值。同时,players本身是数组,直接push会导致缓存变成[[...players]]的结构,和初始数据格式不一致。
正确写法:
如果需要完全替换旧数据,可直接返回新数组:
updateCachedData(() => players);
如果需要修改现有数组(比如增量更新),则直接操作draft:
updateCachedData((draft) => { // 清空原数组 draft.splice(0, draft.length); // 批量添加新元素 draft.push(...players); });
完整修正后的API代码
export const teamPlayersApi = createApi({ reducerPath: "teamPlayers", baseQuery: fakeBaseQuery(), tagTypes: ["TeamPlayer"], endpoints: (builder) => ({ fetchTeamPlayersStreambyMatchId: builder.query({ async queryFn(matchId) { try { const teamPlayersRef = collection(db, "teamPlayers"); const tpq = query( teamPlayersRef, where("playerMatchId", "==", matchId) ); const querySnap = await getDocs(tpq); const players = querySnap.docs.map((doc) => ({ id: doc.id, player: doc.data() })); return { data: players }; } catch (error) { console.error(error.message); return { error: error.message }; } }, async onCacheEntryAdded( matchId, { updateCachedData, cacheDataLoaded, cacheEntryRemoved } ) { let unsubscribe = () => {}; try { await cacheDataLoaded; // 等待初始缓存加载完成 const playersRef = query( collection(db, "teamPlayers"), where("playerMatchId", "==", matchId) ); // 订阅实时更新 unsubscribe = onSnapshot(playersRef, (snapshot) => { const players = snapshot.docs.map((doc) => ({ id: doc.id, player: doc.data() })); // 更新缓存 updateCachedData(() => players); }); } catch (err) { console.error("实时订阅出错:", err); } // 缓存失效时取消订阅 await cacheEntryRemoved; unsubscribe(); } }) }) });
关键注意点
onCacheEntryAdded里必须等待cacheDataLoaded,确保初始数据已经存入缓存后再启动实时监听,避免数据覆盖混乱。- 一定要在
cacheEntryRemoved后调用unsubscribe(),这是解决内存泄漏的核心。 - 保持缓存数据的格式和初始
queryFn返回的格式一致,否则组件接收的数据结构变化可能导致渲染异常。
内容的提问来源于stack exchange,提问作者Sappy3339
相关产品推荐
相关产品推荐

