RTK Query 中为什么需要定义 tagTypes 还要再定义 providesTags

5 阅读2分钟

在 RTK Query(Redux Toolkit Query)中,tagTypesprovidesTags(以及 mutation 中的 invalidatesTags)是缓存失效(cache invalidation)机制的核心部分,它们分工不同,看似“重复”其实是为了实现类型安全灵活的粒度控制自动重获取

为什么需要先定义 tagTypes?

  • tagTypes 是你在 createApi 时全局声明的标签类型列表(数组字符串),例如:
    const api = createApi({
      tagTypes: ['Post', 'User'],  // 这里声明可能的标签类型
      // ...
    });
    
  • 作用:
    • 定义该 API slice 中允许使用的所有标签类型
    • 提供 TypeScript 类型检查:在 endpoints 中使用 providesTags/invalidatesTags 时,标签的 type 必须来自 tagTypes,否则会报类型错误(避免拼写错或随意发明标签)。
    • 它是静态声明,让 RTK Query 知道这个 API 涉及哪些实体类型的数据,便于内部管理和代码分割(injectEndpoints 时也可通过 enhanceEndpoints 添加)。

如果不定义 tagTypes,直接在 providesTags 用字符串,TypeScript 会报错(type 被推断为 neverstring 不匹配)。

为什么还要在每个 endpoint 定义 providesTags?

  • providesTags动态的,在具体 endpoint(通常是 query)中声明这个 endpoint 的缓存数据提供了哪些标签
  • 例如:
    getPosts: build.query({
      query: () => '/posts',
      providesTags: (result) => 
        result 
          ? [...result.map(({ id }) => ({ type: 'Post', id })), { type: 'Post', id: 'LIST' }]
          : [{ type: 'Post', id: 'LIST' }],
    }),
    
  • 作用:
    • 为当前查询的缓存条目附加标签,告诉 RTK Query “这个缓存数据依赖于这些标签”。
    • 支持细粒度控制
      • 简单字符串如 'Post':表示整个列表或所有 Post 数据(无效化时会重获取全部)。
      • 带 id 的对象 { type: 'Post', id: 123 }:表示具体某个 Post 实例(无效化时只重获取该项,避免不必要的全量重获取)。
    • 可以是函数,根据返回结果动态生成标签(常见于列表查询)。

它们如何协作实现自动重获取?

  • Query endpoint 用 providesTags 标记缓存“提供”了哪些标签。
  • Mutation endpoint 用 invalidatesTags 标记这个操作会“失效”哪些标签。
  • 当 mutation 执行成功后,RTK Query 会自动查找所有提供了匹配标签的缓存条目,并:
    • 如果组件还在订阅该数据 → 自动重获取(refetch)。
    • 否则 → 从缓存移除。

示例:

  • getPosts providesTags: ['Post'] 或带 id 的标签。
  • addPost mutation invalidatesTags: ['Post'] → 添加新帖后,自动重获取帖子列表。

总结为什么“都需要”

  • tagTypes:全局、静态,负责类型安全标签规范(像“枚举”)。
  • providesTags:局部、动态,负责具体缓存关联失效粒度(像“实例标注”)。
  • 没有 tagTypes,providesTags 就失去类型保护;没有 providesTags,mutation 的 invalidatesTags 就不知道要失效哪些缓存。

这种设计让缓存管理既安全又强大,避免手动 refetch,同时支持从“全列表失效”到“单个项失效”的灵活场景。更多细节可参考官方文档的 Automated Re-fetching 部分。