跳到主要内容

 

变更

概述

变更用于向服务器发送数据更新并将更改应用到本地缓存。变更也可以使缓存数据失效并强制重新获取。

定义变更端点

通过在 createApiendpoints 部分返回一个对象,并使用 build.mutation() 方法定义字段,来定义变更端点。

变更端点应定义一个构造 URL(包括任何 URL 查询参数)的 query 回调,或者一个可能执行任意异步逻辑并返回结果的 queryFn 回调query 回调也可以返回一个包含 URL、要使用的 HTTP 方法和请求体的对象。

如果 query 回调需要额外的数据来生成 URL,它应被编写为接受一个参数。如果你需要传入多个参数,将它们格式化为一个 "options object"。

变更端点也可以在结果被缓存之前修改响应内容,定义 "tags" 来标识缓存失效,并提供缓存条目生命周期回调以在缓存条目被添加和删除时运行额外的逻辑。

当与 TypeScript 一起使用时,你应该为返回类型和预期的查询参数提供泛型:build.mutation<ReturnType, ArgType>。如果没有参数,使用 void 作为参数类型。

所有变更端点选项的示例
// 文件: types.ts noEmit
export interface Post {
id: number
name: string
}

// 文件: api.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post } from './types'

const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
// 变更接受一个 `Partial<Post>` 参数,并返回一个 `Post`
updatePost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
// 注意:可选的 `queryFn` 可以代替 `query` 使用
query: ({ id, ...patch }) => ({
url: `post/${id}`,
method: 'PATCH',
body: patch,
}),
// 在钩子或选择器中挑选数据并防止嵌套属性
transformResponse: (response: { data: Post }, meta, arg) => response.data,
// 在钩子或选择器中挑选错误并防止嵌套属性
transformErrorResponse: (
response: { status: string | number },
meta,
arg,
) => response.status,
invalidatesTags: ['Post'],
// onQueryStarted 对于乐观更新很有用
// 第二个参数是解构的 `MutationLifecycleApi`
async onQueryStarted(
arg,
{ dispatch, getState, queryFulfilled, requestId, extra, getCacheEntry },
) {},
// 第二个参数是解构的 `MutationCacheLifecycleApi`
async onCacheEntryAdded(
arg,
{
dispatch,
getState,
extra,
requestId,
cacheEntryRemoved,
cacheDataLoaded,
getCacheEntry,
},
) {},
}),
}),
})
信息

onQueryStarted 方法可以用于乐观更新

使用 React 钩子执行变更

变更钩子行为

useQuery 不同,useMutation 返回一个元组。元组的第一个元素是 "触发" 函数,第二个元素包含 statuserrordata 的对象。

useQuery 钩子不同,useMutation 钩子不会自动执行。要运行变更,你必须调用钩子返回的元组值中的第一个触发函数。

查看 useMutation 以获取钩子签名和其他详细信息。

常用的 Mutation Hook 返回值

useMutation hook 返回一个包含 "mutation 触发器" 函数的元组,以及一个包含 "mutation 结果" 的属性的对象。

"mutation 触发器" 是一个函数,当被调用时,将触发该端点的 mutation 请求。调用 "mutation 触发器" 会返回一个带有 unwrap 属性的 promise,该属性可以被调用以解包 mutation 调用并提供原始响应/错误。如果你希望在调用站点内部确定 mutation 是否成功/失败,这可能会很有用。

"mutation 结果" 是一个包含属性的对象,如最新的 mutation 请求的 data,以及当前请求生命周期状态的状态布尔值。

以下是 "mutation 结果" 对象上最常用的一些属性。请参考 useMutation 以获取所有返回属性的详细列表。

  • data - 如果存在,返回最新触发响应的数据。如果从同一 hook 实例调用后续触发器,此将返回 undefined,直到接收到新数据。如果需要前一个响应数据以平滑过渡到新数据,请考虑组件级缓存。
  • error - 如果存在,返回错误结果。
  • isUninitialized - 当为 true 时,表示 mutation 尚未触发。
  • isLoading - 当为 true 时,表示 mutation 已触发并正在等待响应。
  • isSuccess - 当为 true 时,表示最后触发的 mutation 有来自成功请求的数据。
  • isError - 当为 true 时,表示最后触发的 mutation 导致错误状态。
  • reset - 一个方法,用于将 hook 重置回其原始状态并从缓存中删除当前结果。
备注

在 RTK Query 中,mutation 不包含 'loading' 和 'fetching' 之间的语义区别,就像 query 一样。对于 mutation,后续调用不被假定为必然相关,所以 mutation 要么是 'loading',要么是 'not loading',没有 're-fetching' 的概念。

共享 Mutation 结果

默认情况下,useMutation hook 的单独实例并不固有地相互关联。 触发一个实例不会影响另一个实例的结果。无论这些 hook 是否在同一组件内调用,还是在不同的组件内调用,都适用。

export const ComponentOne = () => {
// 触发 `updatePostOne` 将影响此组件中的结果,
// 但不会影响 `ComponentTwo` 中的结果,反之亦然
const [updatePost, result] = useUpdatePostMutation()

return <div>...</div>
}

export const ComponentTwo = () => {
const [updatePost, result] = useUpdatePostMutation()

return <div>...</div>
}

RTK Query 提供了一个选项,使用 fixedCacheKey 选项在 mutation hook 实例之间共享结果。 任何具有相同 fixedCacheKey 字符串的 useMutation hook 在调用任何触发函数时都会彼此共享结果。这应该是你希望共享结果的每个 mutation hook 实例之间共享的唯一字符串。

export const ComponentOne = () => {
// 触发 `updatePostOne` 将影响这个组件中的结果,
// 以及 `ComponentTwo` 中的结果,反之亦然
const [updatePost, result] = useUpdatePostMutation({
fixedCacheKey: 'shared-update-post',
})

return <div>...</div>
}

export const ComponentTwo = () => {
const [updatePost, result] = useUpdatePostMutation({
fixedCacheKey: 'shared-update-post',
})

return <div>...</div>
}
备注

使用 fixedCacheKey 时,originalArgs 属性无法共享,将始终为 undefined

标准 Mutation 示例

这是一个修改版的完整示例,你可以在页面底部看到,以突出 updatePost mutation。在这个场景中,使用 useQuery 获取一个帖子,然后渲染一个 EditablePostName 组件,允许我们编辑帖子的名字。

src/features/posts/PostDetail.tsx
export const PostDetail = () => {
const { id } = useParams<{ id: any }>()

const { data: post } = useGetPostQuery(id)

const [
updatePost, // 这是 mutation 触发器
{ isLoading: isUpdating }, // 这是解构的 mutation 结果
] = useUpdatePostMutation()

return (
<Box p={4}>
<EditablePostName
name={post.name}
onUpdate={(name) => {
// 如果你想立即访问 mutation 的结果,你需要链式调用 `.unwrap()`
// 如果你实际上想要 payload 或捕获错误。
// 示例:`updatePost().unwrap().then(fulfilled => console.log(fulfilled)).catch(rejected => console.error(rejected))

return (
// 使用 `id` 和更新的 `name` 执行触发器
updatePost({ id, name })
)
}}
isLoading={isUpdating}
/>
</Box>
)
}

高级 Mutations 与重新验证

在现实世界中,开发者在执行 mutation 后希望将本地数据缓存与服务器重新同步(即 "重新验证")是非常常见的。RTK Query 采取了更集中的方法,要求你在 API 服务定义中配置失效行为。请参阅 使用抽象标签 ID 的高级失效 以获取有关 RTK Query 的高级失效处理的详细信息。

重新验证示例

这是一个CRUD服务的示例,用于处理帖子。它实现了选择性无效化列表策略,很可能会为真实应用程序提供良好的基础。

src/app/services/posts.ts
// 如果不使用自动生成的钩子,可以从 '@reduxjs/toolkit/query' 导入
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export interface Post {
id: number
name: string
}

type PostsResponse = Post[]

export const postApi = createApi({
reducerPath: 'postsApi',
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => 'posts',
// 提供一个按 `id` 列出的 `Posts` 列表。
// 如果执行了任何 `invalidate` 这些标签的变异,这个查询将重新运行,以保持始终最新。
// `LIST` id 是我们刚刚编造的一个"虚拟id",以便在添加新的 `Posts` 元素时特别无效化这个查询。
providesTags: (result) =>
// 结果是否可用?
result
? // 查询成功
[
...result.map(({ id }) => ({ type: 'Posts', id }) as const),
{ type: 'Posts', id: 'LIST' },
]
: // 发生了错误,但我们仍然希望在 `{ type: 'Posts', id: 'LIST' }` 被无效化时重新获取这个查询
[{ type: 'Posts', id: 'LIST' }],
}),
addPost: build.mutation<Post, Partial<Post>>({
query(body) {
return {
url: `post`,
method: 'POST',
body,
}
},
// 无效化所有提供 `LIST` id 的 Post 类型查询 - 毕竟,根据排序顺序,
// 新创建的帖子可能会出现在任何列表中。
invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
}),
getPost: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
updatePost: build.mutation<Post, Partial<Post>>({
query(data) {
const { id, ...body } = data
return {
url: `post/${id}`,
method: 'PUT',
body,
}
},
// 仅无效化订阅此 Post `id` 的所有查询。
// 在这种情况下,`getPost` 将被重新运行。如果这个 id 在其结果下,`getPosts` *可能* 会重新运行。
invalidatesTags: (result, error, { id }) => [{ type: 'Posts', id }],
}),
deletePost: build.mutation<{ success: boolean; id: number }, number>({
query(id) {
return {
url: `post/${id}`,
method: 'DELETE',
}
},
// 仅无效化订阅此 Post `id` 的所有查询。
invalidatesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})

export const {
useGetPostsQuery,
useAddPostMutation,
useGetPostQuery,
useUpdatePostMutation,
useDeletePostMutation,
} = postApi