You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 01:50:15