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

Firebase RTDB触发变更事件时如何更新RTK Query缓存?

问题:结合RTK Query缓存与Firebase实时订阅自动更新数据

我正在使用Redux Toolkit、RTK Query(用于查询Firebase及其他API)和Firebase(处理认证与数据库)。现有代码能正常获取并缓存数据,但希望同时利用RTK Query的缓存功能和Firebase的事件订阅机制,让数据库发生变更(无论来自任何来源,甚至直接在Firebase控制台操作)时,自动更新RTK Query的缓存。我试过updateQueryCache和invalidateTags方法,但没找到理想方案,求指点方向。


现有代码

firebase.ts

export const onRead = (
  collection: string,
  callback: (snapshort: DataSnapshot) => void,
  options: ListenOptions = { onlyOnce: false }
) => onValue(ref(db, collection), callback, options);

export async function getCollection<T>(
  collection: string,
  onlyOnce: boolean = false
): Promise<T> {
  let timeout: NodeJS.Timeout;
  return new Promise<T>((resolve, reject) => {
    timeout = setTimeout(() => reject('Request timed out!'), ASYNC_TIMEOUT);
    onRead(collection, (snapshot) => resolve(snapshot.val()), { onlyOnce });
  }).finally(() => clearTimeout(timeout));
}

awards.ts

const awards = dbApi
  .enhanceEndpoints({ addTagTypes: ['Themes'] })
  .injectEndpoints({
    endpoints: (builder) => ({
      getThemes: builder.query<ThemeData[], void>({
        async queryFn(arg, api) {
          try {
            const { auth } = api.getState() as RootState;
            const programme = auth.user?.unit.guidingProgramme!;
            const path = `/themes/${programme}`;
            const themes = await getCollection<ThemeData[]>(path, true);
            return { data: themes };
          } catch (error) {
            return { error: error as FirebaseError };
          }
        },
        providesTags: ['Themes'],
        keepUnusedDataFor: 1000 * 60
      }),

      getTheme: builder.query<ThemeData, string | undefined>({
        async queryFn(slug, api) {
          try {
            const initiate = awards.endpoints.getThemes.initiate;
            const getThemes = api.dispatch(initiate());
            const { data } = (await getThemes) as ApiResponse<ThemeData[]>;
            const name = slug
              ?.split('-')
              .map(
                (value) =>
                  value.substring(0, 1).toUpperCase() +
                  value.substring(1).toLowerCase()
              )
              .join(' ');

            return { data: data?.find((theme) => theme.name === name) };
          } catch (error) {
            return { error: error as FirebaseError };
          }
        },
        keepUnusedDataFor: 0
      })
    })
  });

解决方案

核心思路是在RTK Query的查询中启动Firebase实时订阅,当数据变化时直接更新RTK Query缓存,同时处理订阅的清理避免内存泄漏。

1. 修改Firebase工具函数,支持取消订阅

让onRead返回取消订阅的函数,方便后续清理:

// firebase.ts
export const onRead = (
  collection: string,
  callback: (snapshot: DataSnapshot) => void,
  options: ListenOptions = { onlyOnce: false }
): (() => void) => {
  const dbRef = ref(db, collection);
  const unsubscribe = onValue(dbRef, callback, options);
  return unsubscribe;
};

2. 改造getThemes端点,添加实时订阅与缓存更新

在queryFn中先获取初始数据,再启动Firebase订阅,数据变化时用updateQueryData直接更新缓存,同时返回abort函数清理订阅:

getThemes: builder.query<ThemeData[], void>({
  async queryFn(arg, api) {
    try {
      const { auth } = api.getState() as RootState;
      const programme = auth.user?.unit.guidingProgramme!;
      const path = `/themes/${programme}`;

      // 获取初始数据,返回给RTK Query初始化缓存
      const initialThemes = await getCollection<ThemeData[]>(path, true);

      // 启动Firebase实时订阅
      const unsubscribe = onRead(path, (snapshot) => {
        const updatedThemes = snapshot.val() as ThemeData[];
        // 直接更新RTK Query缓存,无需重新请求
        api.dispatch(
          awards.util.updateQueryData('getThemes', arg, (draft) => {
            // 根据数据结构选择更新方式,这里直接替换数组
            draft.splice(0, draft.length, ...updatedThemes);
          })
        );
      });

      // 返回初始数据+清理函数
      return {
        data: initialThemes,
        abort: unsubscribe // 查询取消时自动调用,清理订阅
      };
    } catch (error) {
      return { error: error as FirebaseError };
    }
  },
  providesTags: ['Themes'],
  keepUnusedDataFor: 1000 * 60
}),

3. 优化getTheme端点,复用缓存数据

getTheme无需重新触发getThemes请求,直接读取缓存数据,并通过标签关联实现自动更新:

getTheme: builder.query<ThemeData | undefined, string | undefined>({
  queryFn(slug, api) {
    try {
      // 从RTK Query缓存中直接获取getThemes的数据
      const themesQuery = api.getState().api.queries['getThemes(undefined)'];
      const themes = themesQuery?.data as ThemeData[];

      if (!themes || !slug) return { data: undefined };

      // 转换slug为主题名称
      const name = slug
        .split('-')
        .map(val => val.charAt(0).toUpperCase() + val.slice(1).toLowerCase())
        .join(' ');

      return { data: themes.find(theme => theme.name === name) };
    } catch (error) {
      return { error: error as FirebaseError };
    }
  },
  // 关联Themes标签,当getThemes缓存更新时自动重新计算结果
  providesTags: (result) => 
    result ? [{ type: 'Themes', id: result.name }] : ['Themes'],
  keepUnusedDataFor: 0
}),

关键说明

  • 使用updateQueryData直接修改缓存,比invalidateTags更高效,避免重复网络请求。
  • 必须返回abort函数,确保组件卸载或查询失效时取消Firebase订阅,防止内存泄漏。
  • getTheme通过标签关联,当getThemes缓存更新时会自动重新执行,保证数据一致性。

内容的提问来源于stack exchange,提问作者Mark Bailey

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 04:35:28