自动重新获取
如在 默认缓存行为 中所述,当为查询端点添加订阅时,只有在缓存数据不存在的情况下才会发送请求。如果存在,则会提供现有数据。
RTK Query 使用“缓存标签”系统来自动重新获取受变异端点影响的数据的查询端点。这使得您可以设计 API,以便触发特定变异会导致某个查询端点认为其缓存数据无效,并在存在活动订阅的情况下重新获取数据。
每个端点 + 参数组合都会贡献其自己的 queryCacheKey
。缓存标签系统使 RTK Query 能够告知特定查询缓存已提供哪些标签。如果触发一个被认为会失效
查询缓存已提供的标签的变异,则缓存数据将被视为失效,如果存在对缓存数据的活动订阅,则会重新获取数据。
有关通过其他方式触发重新获取的信息,请参阅 操作缓存行为。
定义
标签
另请参阅:tagTypes API 参考
对于 RTK Query,标签只是您可以为特定数据集合提供的名称,以控制缓存和失效行为,以便重新获取数据。它可以被视为附加到缓存数据的“标签”,在变异后读取,以决定数据是否应该受到变异的影响。
标签在定义 API 时在 tagTypes
参数中定义。例如,在一个同时包含 Posts
和 Users
的应用程序中,您可以在调用 createApi
时定义 tagTypes: ['Post', 'User']
。
单个 tag
具有一个 type
,表示为 string
名称,以及一个可选的 id
,表示为 string
或 number
。它可以表示为一个简单的字符串(例如 'Post'
),也可以表示为一个形状为 {type: string, id?: string|number}
的对象(例如 [{type: 'Post', id: 1}]
)。
提供标签
另请参阅:providesTags API 参考
一个查询可以使其缓存的数据提供标签。这样做决定了哪个“标签”附加到查询返回的缓存数据。
providesTags
参数可以是 string
数组(例如 ['Post']
)、{type: string, id?: string|number}
(例如 [{type: 'Post', id: 1}]
)或返回此类数组的回调函数。该函数将以结果作为第一个参数、响应错误作为第二个参数以及最初传递给 query
方法的参数作为第三个参数。请注意,根据查询是否成功,结果或错误参数可能未定义。
失效标签
一个变异可以根据标签失效特定缓存数据。这样做决定了哪些缓存数据将被重新获取或从缓存中删除。
invalidatesTags
参数可以是 string
数组(例如 ['Post']
)、{type: string, id?: string|number}
(例如 [{type: 'Post', id: 1}]
),或者是一个返回此类数组的回调函数。该函数将以结果作为第一个参数、响应错误作为第二个参数,以及最初传递给 query
方法的参数作为第三个参数。请注意,结果或错误参数可能根据突变是否成功而被定义为未定义。
缓存标签
RTK Query 使用“标签”的概念来确定一个端点的突变是否意图失效来自另一个端点的查询提供的某些数据。
如果缓存数据被失效,它将要么重新获取提供查询(如果组件仍在使用该数据),要么从缓存中删除数据。
在定义 API 切片时,createApi
接受一个标签类型名称数组作为 tagTypes
属性,这是一个 API 切片查询可能提供的可能标签名称选项列表。
下面的示例声明端点可能向缓存提供“帖子”和/或“用户”。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => '/posts',
}),
getUsers: build.query<User[], void>({
query: () => '/users',
}),
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
}),
getUsers: build.query({
query: () => '/users',
}),
addPost: build.mutation({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
}),
editPost: build.mutation({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
通过将这些标签声明为可能提供给缓存的内容,它使单个突变端点能够控制它们是否影响缓存的特定部分,以及与单个端点上的 providesTags
和 invalidatesTags
结合使用。
提供缓存数据
每个单独的 query
端点都可以让其缓存数据提供特定的标签。这样做可以在一个或多个查询端点的缓存数据和一个或多个突变端点的行为之间建立关系。
query
端点上的 providesTags
属性用于此目的。
提供的标签在不同的 query
端点之间没有内在关系。提供的标签用于确定由端点返回的缓存数据是否应该被 失效
,以及是重新获取还是从缓存中删除。如果两个单独的端点提供相同的标签,它们仍然会贡献它们自己不同的缓存数据,这些数据后来都可能被来自突变的单个标签失效。
下面的示例声明 getPosts
query
端点使用 query
端点的 providesTags
属性向缓存 提供
'Post'
标签。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => '/posts',
providesTags: ['Post'],
}),
getUsers: build.query<User[], void>({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'posts',
method: 'POST',
body,
}),
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
providesTags: ['Post'],
}),
getUsers: build.query({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation({
query: (body) => ({
url: 'posts',
method: 'POST',
body,
}),
}),
editPost: build.mutation({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
为了更精细地控制提供的数据,提供的tags
可以关联一个id
。这使得能够区分“任何特定标签类型”和“特定标签类型的特定实例”。
下面的示例声明,提供的帖子与特定 ID 相关联,这些 ID 由端点返回的结果确定
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post' as const, id })), 'Post']
: ['Post'],
}),
getUsers: build.query<User[], void>({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post', id })), 'Post']
: ['Post'],
}),
getUsers: build.query({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
}),
editPost: build.mutation({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
请注意,对于上面的示例,id
在成功的结果中尽可能地使用。在发生错误的情况下,不会提供结果,我们仍然认为它提供了通用'Post'
标签类型,而不是该标签的任何特定实例。
为了更强地控制失效适当的数据,您可以为给定标签使用任意 ID,例如'LIST'
。有关更多详细信息,请参阅使用抽象标签 ID 的高级失效。
失效缓存数据
每个单独的变异端点都可以invalidate
现有缓存数据的特定标签。这样做使一个或多个查询端点的缓存数据与一个或多个变异端点的行为之间建立关系。
变异端点上的invalidatesTags
属性用于此目的。
下面的示例声明,addPost
和editPost
变异端点invalidate
任何带有'Post'
标签的缓存数据,使用变异端点的invalidatesTags
属性
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post' as const, id })), 'Post']
: ['Post'],
}),
getUsers: build.query<User[], void>({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post', id })), 'Post']
: ['Post'],
}),
getUsers: build.query({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
editPost: build.mutation({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
}),
})
对于上面的示例,这告诉 RTK Query,在addPost
和/或editPost
变异调用并完成之后,任何使用'Post'
标签提供的缓存数据将不再有效。如果组件当前订阅了上述变异调用并完成之后'Post'
标签的缓存数据,它将自动重新获取以从服务器检索最新数据。
一个示例场景如下所示
- 渲染一个组件,该组件使用
useGetPostsQuery()
钩子订阅该端点的缓存数据 /posts
请求被发出,服务器响应带有 ID 为 1、2 和 3 的帖子getPosts
端点将接收到的数据存储在缓存中,并在内部注册已提供的以下标签[
{ type: 'Post', id: 1 },
{ type: 'Post', id: 2 },
{ type: 'Post', id: 3 },
]editPost
变异被触发以更改特定帖子- 完成后,RTK Query 在内部注册
'Post'
标签现在已失效,并从缓存中删除先前提供的'Post'
标签 - 由于
getPosts
端点已提供类型为'Post'
的标签,而该标签现在具有无效的缓存数据,并且组件仍然订阅了该数据,因此/posts
请求会自动再次触发,获取新数据并为更新的缓存数据注册新标签
为了更精细地控制失效数据,失效的 tags
可以与 providesTags
相同的方式关联一个 id
。这使得能够区分“特定标签类型的任何一个”和“特定标签类型的特定实例”。
以下示例声明 editPost
变异使 Post
标签的特定实例失效,使用调用变异函数时传入的 ID
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post' as const, id })), 'Post']
: ['Post'],
}),
getUsers: build.query<User[], void>({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
invalidatesTags: (result, error, arg) => [{ type: 'Post', id: arg.id }],
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post', id })), 'Post']
: ['Post'],
}),
getUsers: build.query({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
editPost: build.mutation({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
invalidatesTags: (result, error, arg) => [{ type: 'Post', id: arg.id }],
}),
}),
})
对于上面的示例,而不是使类型为 'Post'
的任何标签失效,调用 editPost
变异函数现在只会使提供的 id
的标签失效。也就是说,如果来自端点的缓存数据没有为同一个 id
提供 'Post'
,它将仍然被视为“有效”,并且不会被触发自动重新获取。
为了更强地控制失效适当的数据,您可以为给定标签使用任意 ID,例如'LIST'
。有关更多详细信息,请参阅使用抽象标签 ID 的高级失效。
标签失效行为
下面的矩阵显示了哪些失效的标签会影响和使哪些提供的标签失效的示例
已提供 已失效 | 通用标签 A ['Post'] / [{ type: 'Post' }] | 通用标签 B ['User'] / [{ type: 'User' }] | 特定标签 A1 [{ 类型: 'Post', id: 1 }] | 特定标签 A2 [{ 类型: 'Post', id: 'LIST' }] | 特定标签 B1 [{ 类型: 'User', id: 1 }] | 特定标签 B2 [{ 类型: 'User', id: 2 }] |
---|---|---|---|---|---|---|
通用标签 A ['Post'] / [{ 类型: 'Post' }] | ✔️ | ✔️ | ✔️ | |||
通用标签 B ['User'] / [{ type: 'User' }] | ✔️ | ✔️ | ✔️ | |||
特定标签 A1 [{ 类型: 'Post', id: 1 }] | ✔️ | |||||
特定标签 A2 [{ 类型: 'Post', id: 'LIST' }] | ✔️ | |||||
特定标签 B1 [{ 类型: 'User', id: 1 }] | ✔️ | |||||
特定标签 B2 [{ 类型: 'User', id: 2 }] | ✔️ |
以下部分根据标签的具体性总结了失效行为。
通用标签
例如:['Post'] / [{ 类型: 'Post' }]
将失效
任何提供
与匹配类型匹配的标签,包括通用标签和特定标签。
示例
如果Post
的通用标签失效,则数据提供
以下标签的端点将全部失效
['Post']
[{ type: 'Post' }]
[{ 类型: 'Post' }, { 类型: 'Post', id: 1 }]
[{ 类型: 'Post', id: 1 }]
[{ 类型: 'Post', id: 1 }, { 类型: 'User' }]
[{ 类型: 'Post', id: 'LIST' }]
[{ 类型: 'Post', id: 1 }, { 类型: 'Post', id: 'LIST' }]
数据提供
以下标签的端点将不会失效
['User']
[{ type: 'User' }]
[{ 类型: 'User', id: 1 }]
[{ 类型: 'User', id: 'LIST' }]
[{ 类型: 'User', id: 1 }, { 类型: 'User', id: 'LIST' }]
特定标签
例如:[{ 类型: 'Post', id: 1 }]
将失效
任何提供
与匹配类型和匹配 ID 匹配的标签。不会直接导致通用
标签失效,但可能使提供通用
标签的端点的失效,如果它也提供匹配的特定
标签。
示例 1:如果{ 类型: 'Post', id: 1 }
的特定标签失效,则数据提供
以下标签的端点将全部失效
[{ 类型: 'Post' }, { 类型: 'Post', id: 1 }]
[{ 类型: 'Post', id: 1 }]
[{ 类型: 'Post', id: 1 }, { 类型: 'User' }]
[{ 类型: 'Post', id: 1 }, { 类型: 'Post', id: 'LIST' }]
数据提供
以下标签的端点将不会失效
['Post']
[{ type: 'Post' }]
[{ 类型: 'Post', id: 'LIST' }]
['User']
[{ type: 'User' }]
[{ 类型: 'User', id: 1 }]
[{ 类型: 'User', id: 'LIST' }]
[{ 类型: 'User', id: 1 }, { 类型: 'User', id: 'LIST' }]
示例 2:如果{ 类型: 'Post', id: 'LIST' }
的特定标签失效,则数据提供
以下标签的端点将全部失效
[{ 类型: 'Post', id: 'LIST' }]
[{ 类型: 'Post', id: 1 }, { 类型: 'Post', id: 'LIST' }]
数据提供
以下标签的端点将不会失效
['Post']
[{ type: 'Post' }]
[{ 类型: 'Post' }, { 类型: 'Post', id: 1 }]
[{ 类型: 'Post', id: 1 }]
[{ 类型: 'Post', id: 1 }, { 类型: 'User' }]
['User']
[{ type: 'User' }]
[{ 类型: 'User', id: 1 }]
[{ 类型: 'User', id: 'LIST' }]
[{ 类型: 'User', id: 1 }, { 类型: 'User', id: 'LIST' }]
食谱
使用抽象标签 ID 的高级失效
虽然使用“实体 ID”作为标签id
是一个常见用例,但id
属性并不局限于仅使用数据库 ID。id
仅仅是为特定标签类型
的特定数据集合标记子集的一种方式。
一个强大的用例是使用像 'LIST'
这样的 ID 作为批量查询提供的数据的标签,以及使用实体 ID 来标识单个项目。这样做允许未来的 mutations
声明它们是否仅在数据包含特定项目(例如 { type: 'Post', id: 5 }
)时使数据失效,或者如果数据是 'LIST'
(例如 { type: 'Post', id: 'LIST' }
)时使数据失效。
LIST
是一个任意的字符串 - 从技术上讲,你可以在这里使用任何你想要的东西,比如ALL
或*
。选择自定义 ID 时,重要的是要确保它不可能与查询结果返回的 ID 冲突。如果你在查询结果中存在未知的 ID,并且不想冒险,你可以选择下面的第 3 点。- 你可以添加多个标签类型以获得更多控制
[{ type: 'Posts', id: 'LIST' }, { type: 'Posts', id: 'SVELTE_POSTS' }, { type: 'Posts', id: 'REACT_POSTS' }]
- 如果你觉得使用像 'LIST' 这样的
id
感觉很奇怪,你总是可以添加另一个tagType
并使它的根失效,但我们建议使用如上所示的id
方法。
我们可以比较下面的场景,看看如何使用 'LIST'
ID 来优化行为。
使所有类型的数据失效
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Post, User } from './types'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: (result) =>
result ? result.map(({ id }) => ({ type: 'Posts', id })) : ['Posts'],
}),
addPost: build.mutation<Post, Partial<Post>>({
query: (body) => ({
url: `post`,
method: 'POST',
body,
}),
invalidatesTags: ['Posts'],
}),
getPost: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})
export const { useGetPostsQuery, useGetPostQuery, useAddPostMutation } = api
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query({
query: () => 'posts',
providesTags: (result) =>
result ? result.map(({ id }) => ({ type: 'Posts', id })) : ['Posts'],
}),
addPost: build.mutation({
query: (body) => ({
url: `post`,
method: 'POST',
body,
}),
invalidatesTags: ['Posts'],
}),
getPost: build.query({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})
export const { useGetPostsQuery, useGetPostQuery, useAddPostMutation } = api
function App() {
const { data: posts } = useGetPostsQuery()
const [addPost] = useAddPostMutation()
return (
<div>
<AddPost onAdd={addPost} />
<PostsList />
{/* Assume each PostDetail is subscribed via `const {data} = useGetPostQuery(id)` */}
<PostDetail id={1} />
<PostDetail id={2} />
<PostDetail id={3} />
</div>
)
}
预期结果
当 addPost
被触发时,它会导致每个 PostDetail
组件回到 isFetching
状态,因为 addPost
使根标签失效,这会导致所有提供 'Posts' 的查询重新运行。在大多数情况下,这可能不是你想要的。想象一下,如果你在屏幕上有 100 个帖子,它们都订阅了 getPost
查询 - 在这种情况下,你会创建 100 个请求,并向你的服务器发送大量不必要的流量,而这正是我们想要避免的!即使用户仍然可以看到最后一次缓存的良好结果,并且可能不会注意到除了浏览器卡顿之外的任何其他问题,你仍然想要避免这种情况。
有选择地使列表失效
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Post, User } from './types'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Posts' as const, id })),
{ type: 'Posts', id: 'LIST' },
]
: [{ type: 'Posts', id: 'LIST' }],
}),
addPost: build.mutation<Post, Partial<Post>>({
query(body) {
return {
url: `post`,
method: 'POST',
body,
}
},
invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
}),
getPost: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})
export const { useGetPostsQuery, useAddPostMutation, useGetPostQuery } = api
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query({
query: () => 'posts',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Posts', id })),
{ type: 'Posts', id: 'LIST' },
]
: [{ type: 'Posts', id: 'LIST' }],
}),
addPost: build.mutation({
query(body) {
return {
url: `post`,
method: 'POST',
body,
}
},
invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
}),
getPost: build.query({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})
export const { useGetPostsQuery, useAddPostMutation, useGetPostQuery } = api
function App() {
const { data: posts } = useGetPostsQuery()
const [addPost] = useAddPostMutation()
return (
<div>
<AddPost onAdd={addPost} />
<PostsList />
{/* Assume each PostDetail is subscribed via `const {data} = useGetPostQuery(id)` */}
<PostDetail id={1} />
<PostDetail id={2} />
<PostDetail id={3} />
</div>
)
}
预期结果
当 addPost
被触发时,它只会导致 PostsList
进入 isFetching
状态,因为 addPost
只使 'LIST'
ID 失效,这会导致 getPosts
重新运行(因为它提供了该特定 ID)。因此,在你的网络标签中,你只会看到 1 个新的 GET /posts
请求。由于单个 getPost
查询没有被使失效,因此它们不会因为 addPost
而重新运行。
如果您希望addPost
变异刷新所有帖子,包括单个PostDetail
组件,同时只发出 1 个新的GET /posts
请求,可以使用 selectFromResult
从查询结果中选择数据。
向缓存提供错误
提供给缓存的信息不仅限于成功的数据获取。该概念可用于告知 RTK Query,当遇到特定失败时,应为该失败的缓存数据提供
特定标签
。然后,另一个端点可以失效
该标签
的数据,告诉 RTK Query 如果组件仍然订阅了失败的数据,则重新尝试之前失败的端点。
以下示例演示了具有以下行为的示例
- 如果查询失败并出现错误代码
401 UNAUTHORIZED
,则提供UNAUTHORIZED
缓存标签 - 如果查询失败并出现其他错误,则提供
UNKNOWN_ERROR
缓存标签 - 启用“登录”变异,该变异成功时将
失效
带有UNAUTHORIZED
标签的数据。
这将触发postById
端点重新触发,如果postById
的最后一次调用遇到了未授权错误,并且- 组件仍然订阅了缓存数据
- 启用“重新获取错误查询”变异,该变异调用时将
失效
带有UNKNOWN_ERROR
标签的数据。
这将触发postById
端点重新触发,如果postById
的最后一次调用遇到了未知错误,并且- 组件仍然订阅了缓存数据
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { Post, LoginResponse } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'UNAUTHORIZED', 'UNKNOWN_ERROR'],
endpoints: (build) => ({
postById: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: (result, error, id) =>
result
? [{ type: 'Post', id }]
: error?.status === 401
? ['UNAUTHORIZED']
: ['UNKNOWN_ERROR'],
}),
login: build.mutation<LoginResponse, void>({
query: () => '/login',
// on successful login, will refetch all currently
// 'UNAUTHORIZED' queries
invalidatesTags: (result) => (result ? ['UNAUTHORIZED'] : []),
}),
refetchErroredQueries: build.mutation<null, void>({
queryFn: () => ({ data: null }),
invalidatesTags: ['UNKNOWN_ERROR'],
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'UNAUTHORIZED', 'UNKNOWN_ERROR'],
endpoints: (build) => ({
postById: build.query({
query: (id) => `post/${id}`,
providesTags: (result, error, id) =>
result
? [{ type: 'Post', id }]
: error?.status === 401
? ['UNAUTHORIZED']
: ['UNKNOWN_ERROR'],
}),
login: build.mutation({
query: () => '/login',
// on successful login, will refetch all currently
// 'UNAUTHORIZED' queries
invalidatesTags: (result) => (result ? ['UNAUTHORIZED'] : []),
}),
refetchErroredQueries: build.mutation({
queryFn: () => ({ data: null }),
invalidatesTags: ['UNKNOWN_ERROR'],
}),
}),
})
抽象化常见的提供/失效用法
为给定 API 片段编写提供
和失效
标签的代码将取决于多个因素,包括
- 后端返回的数据形状
- 您期望给定查询端点提供的标签
- 您期望给定变异端点失效的标签
- 您希望使用失效功能的程度
在声明 API 片段时,您可能会觉得代码重复。例如,对于两个都提供特定实体列表的独立端点,providesTags
声明可能只在提供的tagType
中有所不同。
例如
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => `posts`,
providesTags: (result) =>
result
? [
{ type: 'Post', id: 'LIST' },
...result.map(({ id }) => ({ type: 'Post' as const, id })),
]
: [{ type: 'Post', id: 'LIST' }],
}),
getUsers: build.query<User[], void>({
query: () => `users`,
providesTags: (result) =>
result
? [
{ type: 'User', id: 'LIST' },
...result.map(({ id }) => ({ type: 'User' as const, id })),
]
: [{ type: 'User', id: 'LIST' }],
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => `posts`,
providesTags: (result) =>
result
? [
{ type: 'Post', id: 'LIST' },
...result.map(({ id }) => ({ type: 'Post', id })),
]
: [{ type: 'Post', id: 'LIST' }],
}),
getUsers: build.query({
query: () => `users`,
providesTags: (result) =>
result
? [
{ type: 'User', id: 'LIST' },
...result.map(({ id }) => ({ type: 'User', id })),
]
: [{ type: 'User', id: 'LIST' }],
}),
}),
})
您可能会发现定义为特定 api 设计的辅助函数有利于减少端点定义中的这种样板代码,例如
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
function providesList<R extends { id: string | number }[], T extends string>(
resultsWithIds: R | undefined,
tagType: T
) {
return resultsWithIds
? [
{ type: tagType, id: 'LIST' },
...resultsWithIds.map(({ id }) => ({ type: tagType, id })),
]
: [{ type: tagType, id: 'LIST' }]
}
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => `posts`,
providesTags: (result) => providesList(result, 'Post'),
}),
getUsers: build.query({
query: () => `users`,
providesTags: (result) => providesList(result, 'User'),
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
function providesList(resultsWithIds, tagType) {
return resultsWithIds
? [
{ type: tagType, id: 'LIST' },
...resultsWithIds.map(({ id }) => ({ type: tagType, id })),
]
: [{ type: tagType, id: 'LIST' }]
}
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => `posts`,
providesTags: (result) => providesList(result, 'Post'),
}),
getUsers: build.query({
query: () => `users`,
providesTags: (result) => providesList(result, 'User'),
}),
}),
})
以下 gist 中展示了为常见的 rest 数据格式设计的各种标签提供/失效抽象示例,包括 typescript 支持,以及对 'LIST' 风格高级标签失效 和 '错误' 风格标签失效 的分解:RTK Query 缓存实用程序。