Usage > Mutations: sending updates to the server">Usage > Mutations: sending updates to the server">
跳至主要内容

突变

概述

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

定义突变端点

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

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

如果query回调函数需要额外的數據來生成URL,它应该被写成接受一个参数。如果你需要传递多个参数,将它们格式化为一个单一的“选项对象”传递。

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

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

所有变异端点选项的示例
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post } from './types'

const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
// The mutation accepts a `Partial<Post>` arg, and returns a `Post`
updatePost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
// note: an optional `queryFn` may be used in place of `query`
query: ({ id, ...patch }) => ({
url: `post/${id}`,
method: 'PATCH',
body: patch,
}),
// Pick out data and prevent nested properties in a hook or selector
transformResponse: (response: { data: Post }, meta, arg) => response.data,
// Pick out errors and prevent nested properties in a hook or selector
transformErrorResponse: (
response: { status: string | number },
meta,
arg
) => response.status,
invalidatesTags: ['Post'],
// onQueryStarted is useful for optimistic updates
// The 2nd parameter is the destructured `MutationLifecycleApi`
async onQueryStarted(
arg,
{ dispatch, getState, queryFulfilled, requestId, extra, getCacheEntry }
) {},
// The 2nd parameter is the destructured `MutationCacheLifecycleApi`
async onCacheEntryAdded(
arg,
{
dispatch,
getState,
extra,
requestId,
cacheEntryRemoved,
cacheDataLoaded,
getCacheEntry,
}
) {},
}),
}),
})
info

onQueryStarted方法可以用于乐观更新

使用React Hooks执行变异

变异Hook行为

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

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

请参阅useMutation以了解钩子签名和更多详细信息。

常用的变异Hook返回值

useMutation钩子返回一个元组,其中包含一个“变异触发”函数,以及一个包含有关“变异结果”属性的对象。

“变异触发”是一个函数,当被调用时,将为该端点触发变异请求。调用“变异触发”返回一个带有unwrap属性的promise,可以调用该属性来解包变异调用并提供原始响应/错误。如果你希望在调用点内确定变异是否成功/失败,这将非常有用。

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

以下是“变异结果”对象上一些最常用的属性。请参阅useMutation以了解所有返回属性的完整列表。

  • data - 如果存在,则为最新触发器响应返回的数据。如果从同一个钩子实例调用后续触发器,则在收到新数据之前,这将返回未定义。如果需要之前的响应数据以平滑过渡到新数据,请考虑组件级缓存。
  • error - 如果存在,则为错误结果。
  • isUninitialized - 为真时,表示突变尚未触发。
  • isLoading - 为真时,表示突变已触发并正在等待响应。
  • isSuccess - 为真时,表示最后触发的突变已从成功请求中获取数据。
  • isError - 为真时,表示最后触发的突变导致错误状态。
  • reset - 一种将钩子重置回其原始状态并从缓存中删除当前结果的方法
注意

使用 RTK Query 时,突变在语义上不区分“加载”和“获取”,就像查询一样。对于突变,后续调用不一定是相关的,因此突变要么是“加载”,要么是“未加载”,没有“重新获取”的概念。

共享突变结果

默认情况下,useMutation 钩子的不同实例之间没有内在关联。触发一个实例不会影响另一个实例的结果。这适用于钩子是在同一个组件中调用还是在不同的组件中调用。

export const ComponentOne = () => {
// Triggering `updatePostOne` will affect the result in this component,
// but not the result in `ComponentTwo`, and vice-versa
const [updatePost, result] = useUpdatePostMutation()

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

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

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

RTK Query 提供了一个选项,可以使用 fixedCacheKey 选项在突变钩子实例之间共享结果。任何具有相同 fixedCacheKey 字符串的 useMutation 钩子将在任何触发函数被调用时相互共享结果。这应该是在您希望共享结果的每个突变钩子实例之间共享的唯一字符串。

export const ComponentOne = () => {
// Triggering `updatePostOne` will affect the result in both this component,
// but as well as the result in `ComponentTwo`, and vice-versa
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

标准突变示例

这是您可以在页面底部看到的完整示例的修改版本,以突出显示 updatePost 突变。在这种情况下,使用 useQuery 获取帖子,然后渲染一个 EditablePostName 组件,允许我们编辑帖子的名称。

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

const { data: post } = useGetPostQuery(id)

const [
updatePost, // This is the mutation trigger
{ isLoading: isUpdating }, // This is the destructured mutation result
] = useUpdatePostMutation()

return (
<Box p={4}>
<EditablePostName
name={post.name}
onUpdate={(name) => {
// If you want to immediately access the result of a mutation, you need to chain `.unwrap()`
// if you actually want the payload or to catch the error.
// Example: `updatePost().unwrap().then(fulfilled => console.log(fulfilled)).catch(rejected => console.error(rejected))

return (
// Execute the trigger with the `id` and updated `name`
updatePost({ id, name })
)
}}
isLoading={isUpdating}
/>
</Box>
)
}

带重新验证的进阶变异

在现实世界中,开发人员通常希望在执行变异(也称为“重新验证”)后将本地数据缓存与服务器同步。RTK Query 采用更集中化的方式来处理这个问题,要求您在 API 服务定义中配置失效行为。有关使用 RTK Query 进行高级失效处理的详细信息,请参阅使用抽象标签 ID 进行高级失效

重新验证示例

这是一个针对帖子的CRUD 服务示例。它实现了选择性失效列表策略,并且很可能成为真实应用程序的良好基础。

src/app/services/posts.ts
// Or from '@reduxjs/toolkit/query' if not using the auto-generated hooks
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',
// Provides a list of `Posts` by `id`.
// If any mutation is executed that `invalidate`s any of these tags, this query will re-run to be always up-to-date.
// The `LIST` id is a "virtual id" we just made up to be able to invalidate this query specifically if a new `Posts` element was added.
providesTags: (result) =>
// is result available?
result
? // successful query
[
...result.map(({ id }) => ({ type: 'Posts', id } as const)),
{ type: 'Posts', id: 'LIST' },
]
: // an error occurred, but we still want to refetch this query when `{ type: 'Posts', id: 'LIST' }` is invalidated
[{ type: 'Posts', id: 'LIST' }],
}),
addPost: build.mutation<Post, Partial<Post>>({
query(body) {
return {
url: `post`,
method: 'POST',
body,
}
},
// Invalidates all Post-type queries providing the `LIST` id - after all, depending of the sort order,
// that newly created post could show up in any lists.
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,
}
},
// Invalidates all queries that subscribe to this Post `id` only.
// In this case, `getPost` will be re-run. `getPosts` *might* rerun, if this id was under its results.
invalidatesTags: (result, error, { id }) => [{ type: 'Posts', id }],
}),
deletePost: build.mutation<{ success: boolean; id: number }, number>({
query(id) {
return {
url: `post/${id}`,
method: 'DELETE',
}
},
// Invalidates all queries that subscribe to this Post `id` only.
invalidatesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})

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